Java集合的学习先理清数据结构:

一、属性

  1. //哈希桶,存放链表。 长度是2的N次方,或者初始化时为0.
  2. transient Node<K,V>[] table;
  3. //最大容量 2的30次方
  4. static final int MAXIMUM_CAPACITY = 1 << 30;
  5. //默认的加载因子
  6. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  7. //加载因子,用于计算哈希表元素数量的阈值。 threshold = 哈希桶.length * loadFactor;
  8. final float loadFactor;
  9. //哈希表内元素数量的阈值,当哈希表内元素数量超过阈值时,会发生扩容resize()。
  10. int threshold;

二、构造函数

  1. public HashMap() {
  2. //默认构造函数,赋值加载因子为默认的0.75f
  3. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  4. }
  5. public HashMap(int initialCapacity) {
  6. //指定初始化容量的构造函数
  7. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  8. }
  9. //同时指定初始化容量 以及 加载因子, 用的很少,一般不会修改loadFactor
  10. public HashMap(int initialCapacity, float loadFactor) {
  11. //边界处理
  12. if (initialCapacity < 0)
  13. throw new IllegalArgumentException("Illegal initial capacity: " +
  14. initialCapacity);
  15. //初始容量最大不能超过2的30次方
  16. if (initialCapacity > MAXIMUM_CAPACITY)
  17. initialCapacity = MAXIMUM_CAPACITY;
  18. //显然加载因子不能为负数
  19. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  20. throw new IllegalArgumentException("Illegal load factor: " +
  21. loadFactor);
  22. this.loadFactor = loadFactor;
  23. //设置阈值为>=初始化容量的 2的n次方的值
  24. this.threshold = tableSizeFor(initialCapacity);
  25. }
  26. //新建一个哈希表,同时将另一个map m 里的所有元素加入表中
  27. public HashMap(Map<? extends K, ? extends V> m) {
  28. this.loadFactor = DEFAULT_LOAD_FACTOR;
  29. putMapEntries(m, false);
  30. }

三、主要方法

get

  1. /**
  2. * get
  3. * 1.先从数组中取,取到hash值相等且equals的,直接返回
  4. * 2.先从数组中取,取到hash值相等且!equals,到链表/红黑树中取
  5. */
  6. // 每一个节点结构
  7. static class Node<K,V> implements Map.Entry<K,V> {
  8. final int hash;
  9. final K key;
  10. V value;
  11. Node<K,V> next;
  12.  
  13. Node(int hash, K key, V value, Node<K,V> next) {
  14. this.hash = hash;
  15. this.key = key;
  16. this.value = value;
  17. this.next = next;
  18. }
  19. }
  20.  
  21. public V get(Object key) {
  22. Node<K,V> e;
  23. return (e = getNode(hash(key), key)) == null ? null : e.value;
  24. }
  25.  
  26. final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab;//Entry对象数组
  27. Node<K,V> first,e; //在tab数组中经过散列的第一个位置
  28. int n;
  29. K k;
  30. /*找到插入的第一个Node,方法是hash值和n-1相与,tab[(n - 1) & hash]*/
  31. //也就是说在一条链上的hash值相同的
  32. if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {
  33. /*检查第一个Node是不是要找的Node*/
  34. if (first.hash == hash && // always check first node
  35. ((k = first.key) == key || (key != null && key.equals(k))))//判断条件是hash值要相同,key值要相同
  36. return first;
  37. /*检查first后面的node*/
  38. if ((e = first.next) != null) {
  39. if (first instanceof TreeNode)
  40. return ((TreeNode<K,V>)first).getTreeNode(hash, key);
  41. /*遍历后面的链表,找到key值和hash值都相同的Node*/
  42. do {
  43. if (e.hash == hash &&
  44. ((k = e.key) == key || (key != null && key.equals(k))))
  45. return e;
  46. } while ((e = e.next) != null);
  47. }
  48. }
  49. return null;
  50. }

put

  1. /**
  2. * put
  3. * 1.数组下标没有对应hash值,直接newNode()添加
  4. * 2.数组下标有对应hash值,添加到链表最后
  5. * 3.链表超过最大长度(8),将链表改为红黑树再添加元素
  6. */
  7. public V put(K key, V value) {
  8. return putVal(hash(key), key, value, false, true);
  9. }
  10.  
  11. final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
  12. //tab存放 当前的哈希桶, p用作临时链表节点
  13. Node<K,V>[] tab; Node<K,V> p; int n, i;
  14. //如果当前哈希表是空的,代表是初始化
  15. if ((tab = table) == null || (n = tab.length) == 0)
  16. //那么直接去扩容哈希表,并且将扩容后的哈希桶长度赋值给n
  17. n = (tab = resize()).length;
  18. //如果当前index的节点是空的,表示没有发生哈希碰撞。 直接构建一个新节点Node,挂载在index处即可。
  19. //这里再啰嗦一下,index 是利用 哈希值 & 哈希桶的长度-1,替代模运算
  20. if ((p = tab[i = (n - 1) & hash]) == null)
  21. tab[i] = newNode(hash, key, value, null);
  22. else {//否则 发生了哈希冲突。
  23. //e
  24. Node<K,V> e; K k;
  25. //如果哈希值相等,key也相等,则是覆盖value操作
  26. if (p.hash == hash &&
  27. ((k = p.key) == key || (key != null && key.equals(k))))
  28. e = p;//将当前节点引用赋值给e
  29. else if (p instanceof TreeNode)//红黑树暂且不谈
  30. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  31. else {//不是覆盖操作,则插入一个普通链表节点
  32. //遍历链表
  33. for (int binCount = 0; ; ++binCount) {
  34. if ((e = p.next) == null) {//遍历到尾部,追加新节点到尾部
  35. p.next = newNode(hash, key, value, null);
  36. //如果追加节点后,链表数量》=8,则转化为红黑树
  37. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  38. treeifyBin(tab, hash);
  39. break;
  40. }
  41. //如果找到了要覆盖的节点
  42. if (e.hash == hash &&
  43. ((k = e.key) == key || (key != null && key.equals(k))))
  44. break;
  45. p = e;
  46. }
  47. }
  48. //如果e不是null,说明有需要覆盖的节点,
  49. if (e != null) { // existing mapping for key
  50. //则覆盖节点值,并返回原oldValue
  51. V oldValue = e.value;
  52. if (!onlyIfAbsent || oldValue == null)
  53. e.value = value;
  54. //这是一个空实现的函数,用作LinkedHashMap重写使用。
  55. afterNodeAccess(e);
  56. return oldValue;
  57. }
  58. }
  59. //如果执行到了这里,说明插入了一个新的节点,所以会修改modCount,以及返回null。
  60.  
  61. //修改modCount
  62. ++modCount;
  63. //更新size,并判断是否需要扩容。
  64. if (++size > threshold)
  65. resize();
  66. //这是一个空实现的函数,用作LinkedHashMap重写使用。
  67. afterNodeInsertion(evict);
  68. return null;
  69. }

remove

  1. /**
  2. * reomve
  3. */
  4. public V remove(Object key) {
  5. Node<K, V> e;
  6. return (e = removeNode(hash(key), key, null, false, true)) == null ? null
  7. : e.value;
  8. }
  9.  
  10. final Node<K, V> removeNode(int hash, Object key, Object value,
  11. boolean matchValue, boolean movable) {
  12. Node<K, V>[] tab;
  13. Node<K, V> p;
  14. int n, index;
  15. if ((tab = table) != null && (n = tab.length) > 0
  16. && (p = tab[index = (n - 1) & hash]) != null) {
  17. Node<K, V> node = null, e;
  18. K k;
  19. V v;
  20. if (p.hash == hash
  21. && ((k = p.key) == key || (key != null && key.equals(k))))
  22. node = p;
  23. else if ((e = p.next) != null) {
  24. if (p instanceof TreeNode)
  25. node = ((TreeNode<K, V>) p).getTreeNode(hash, key);
  26. else {
  27. do {
  28. if (e.hash == hash
  29. && ((k = e.key) == key || (key != null && key
  30. .equals(k)))) {
  31. node = e;
  32. break;
  33. }
  34. p = e;
  35. } while ((e = e.next) != null);
  36. }
  37. }
  38. if (node != null
  39. && (!matchValue || (v = node.value) == value || (value != null && value
  40. .equals(v)))) {
  41. if (node instanceof TreeNode)
  42. ((TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
  43. else if (node == p)
  44. tab[index] = node.next;
  45. else
  46. p.next = node.next;
  47. ++modCount;
  48. --size;
  49. afterNodeRemoval(node);
  50. return node;
  51. }
  52. }
  53. return null;
  54. }

resize

  1. final Node<K,V>[] resize() {
  2. //oldTab 为当前表的哈希桶
  3. Node<K,V>[] oldTab = table;
  4. //当前哈希桶的容量 length
  5. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  6. //当前的阈值
  7. int oldThr = threshold;
  8. //初始化新的容量和阈值为0
  9. int newCap, newThr = 0;
  10. //如果当前容量大于0
  11. if (oldCap > 0) {
  12. //如果当前容量已经到达上限
  13. if (oldCap >= MAXIMUM_CAPACITY) {
  14. //则设置阈值是2的31次方-1
  15. threshold = Integer.MAX_VALUE;
  16. //同时返回当前的哈希桶,不再扩容
  17. return oldTab;
  18. }//否则新的容量为旧的容量的两倍。
  19. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  20. oldCap >= DEFAULT_INITIAL_CAPACITY)//如果旧的容量大于等于默认初始容量16
  21. //那么新的阈值也等于旧的阈值的两倍
  22. newThr = oldThr << 1; // double threshold
  23. }//如果当前表是空的,但是有阈值。代表是初始化时指定了容量、阈值的情况
  24. else if (oldThr > 0) // initial capacity was placed in threshold
  25. newCap = oldThr;//那么新表的容量就等于旧的阈值
  26. else {}//如果当前表是空的,而且也没有阈值。代表是初始化时没有任何容量/阈值参数的情况 // zero initial threshold signifies using defaults
  27. newCap = DEFAULT_INITIAL_CAPACITY;//此时新表的容量为默认的容量 16
  28. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//新的阈值为默认容量16 * 默认加载因子0.75f = 12
  29. }
  30. if (newThr == 0) {//如果新的阈值是0,对应的是 当前表是空的,但是有阈值的情况
  31. float ft = (float)newCap * loadFactor;//根据新表容量 和 加载因子 求出新的阈值
  32. //进行越界修复
  33. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  34. (int)ft : Integer.MAX_VALUE);
  35. }
  36. //更新阈值
  37. threshold = newThr;
  38. @SuppressWarnings({"rawtypes","unchecked"})
  39. //根据新的容量 构建新的哈希桶
  40. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  41. //更新哈希桶引用
  42. table = newTab;
  43. //如果以前的哈希桶中有元素
  44. //下面开始将当前哈希桶中的所有节点转移到新的哈希桶中
  45. if (oldTab != null) {
  46. //遍历老的哈希桶
  47. for (int j = 0; j < oldCap; ++j) {
  48. //取出当前的节点 e
  49. Node<K,V> e;
  50. //如果当前桶中有元素,则将链表赋值给e
  51. if ((e = oldTab[j]) != null) {
  52. //将原哈希桶置空以便GC
  53. oldTab[j] = null;
  54. //如果当前链表中就一个元素,(没有发生哈希碰撞)
  55. if (e.next == null)
  56. //直接将这个元素放置在新的哈希桶里。
  57. //注意这里取下标 是用 哈希值 与 桶的长度-1 。 由于桶的长度是2的n次方,这么做其实是等于 一个模运算。但是效率更高
  58. newTab[e.hash & (newCap - 1)] = e;
  59. //如果发生过哈希碰撞 ,而且是节点数超过8个,转化成了红黑树(暂且不谈 避免过于复杂, 后续专门研究一下红黑树)
  60. else if (e instanceof TreeNode)
  61. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  62. //如果发生过哈希碰撞,节点数小于8个。则要根据链表上每个节点的哈希值,依次放入新哈希桶对应下标位置。
  63. else { // preserve order
  64. //因为扩容是容量翻倍,所以原链表上的每个节点,现在可能存放在原来的下标,即low位, 或者扩容后的下标,即high位。 high位= low位+原哈希桶容量
  65. //低位链表的头结点、尾节点
  66. Node<K,V> loHead = null, loTail = null;
  67. //高位链表的头节点、尾节点
  68. Node<K,V> hiHead = null, hiTail = null;
  69. Node<K,V> next;//临时节点 存放e的下一个节点
  70. do {
  71. next = e.next;
  72. //这里又是一个利用位运算 代替常规运算的高效点: 利用哈希值 与 旧的容量,结果只有两种 0或者oldCap,结果是0则存放在低位,否则存放在高位
  73. if ((e.hash & oldCap) == 0) {
  74. //给头尾节点指针赋值
  75. if (loTail == null)
  76. loHead = e;
  77. else
  78. loTail.next = e;
  79. loTail = e;
  80. }//高位也是相同的逻辑
  81. else {
  82. if (hiTail == null)
  83. hiHead = e;
  84. else
  85. hiTail.next = e;
  86. hiTail = e;
  87. }//循环直到链表结束
  88. } while ((e = next) != null);
  89. //将低位链表存放在原index处,
  90. if (loTail != null) {
  91. loTail.next = null;
  92. newTab[j] = loHead;
  93. }
  94. //将高位链表存放在新index处
  95. if (hiTail != null) {
  96. hiTail.next = null;
  97. newTab[j + oldCap] = hiHead;
  98. }
  99. }
  100. }
  101. }
  102. }
  103. return newTab;
  104. }

遍历

  1. /**
  2. * 遍历 主要看方法nextNode()
  3. */
  4. final class KeyIterator extends HashIterator implements Iterator<K> {
  5. public final K next() {
  6. return nextNode().key;
  7. }
  8. }
  9.  
  10. final class ValueIterator extends HashIterator implements Iterator<V> {
  11. public final V next() {
  12. return nextNode().value;
  13. }
  14. }
  15.  
  16. final class EntryIterator extends HashIterator implements
  17. Iterator<Map.Entry<K, V>> {
  18. public final Map.Entry<K, V> next() {
  19. return nextNode();
  20. }
  21. }
  22.  
  23. abstract class HashIterator {
  24. Node<K, V> next; // next entry to return
  25. Node<K, V> current; // current entry
  26. int expectedModCount; // for fast-fail
  27. int index; // current slot
  28.  
  29. HashIterator() {
  30. expectedModCount = modCount;
  31. Node<K, V>[] t = table;
  32. current = next = null;
  33. index = 0;
  34. if (t != null && size > 0) { // advance to first entry
  35. do {
  36. } while (index < t.length && (next = t[index++]) == null);
  37. }
  38. }
  39.  
  40. public final boolean hasNext() {
  41. return next != null;
  42. }
  43.  
  44. final Node<K, V> nextNode() {
  45. Node<K, V>[] t;
  46. Node<K, V> e = next;
  47. if (modCount != expectedModCount)
  48. throw new ConcurrentModificationException();
  49. if (e == null)
  50. throw new NoSuchElementException();
  51. if ((next = (current = e).next) == null && (t = table) != null) {
  52. do {
  53. } while (index < t.length && (next = t[index++]) == null);
  54. }
  55. return e;
  56. }
  57.  
  58. public final void remove() {
  59. Node<K, V> p = current;
  60. if (p == null)
  61. throw new IllegalStateException();
  62. if (modCount != expectedModCount)  
  63. throw new ConcurrentModificationException();
  64. current = null;
  65. K key = p.key;
  66. removeNode(hash(key), key, null, false, false);
  67. expectedModCount = modCount;
  68. }
  69. }

四、数组的长度是2的次幂? 数组下表计算e.hash & (table.length- 1)? hash()方法(h = key.hashCode()) ^ (h >>> 16)?

1、tableSizeFor(int cap)保证数组容量是2的次幂

  1. static final int tableSizeFor(int cap) {
  2. int n = cap - 1;
  3. n |= n >>> 1;
  4. n |= n >>> 2;
  5. n |= n >>> 4;
  6. n |= n >>> 8;
  7. n |= n >>> 16;
  8. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  9. }

2、2的次幂-1(即table.length- 1)得到是数用二进制表示每一位都是1。

3、将e.hash放进table数组中,需要e.hash%(table.length- 1)得到下标;

这里用e.hash&(table.length- 1)代替e.hash%(table.length- 1),位运算代替除法;

e.hash&(table.length- 1)类似Integer类的toUnsignedLong() 方法:((long) x) & 0xffffffffL,只保留低位;

4、因为e.hash&(table.length- 1)时,比(table.length- 1)高的位都成0了,只用到了e.hash的低位;

e.hash = (h = key.hashCode()) ^ (h >>> 16),使key的hashCode值高16位不变,低16位 由(高16位)^(低16位)得到;

e.hash&(table.length- 1)时用到的e.hash的低位也有高16位参与进来,减少了冲突碰撞。

举例可参考:HashMap的hash()

参考资料:

Java中HashMap底层实现原理(JDK1.8)源码分析

HashMap实现原理及源码分析

面试必备:HashMap源码解析(JDK8)

HashMap源代码分析(JDK1.8)

面试必考:HashMap容量为2次幂的原因

HashMap的hash()

HashMap 实现原理

JDK1.8 HashMap源码分析

JDK源码学习笔记——HashMap的更多相关文章

  1. JDK源码学习笔记——LinkedHashMap

    HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序. LinkedHashMap保证了元素迭代的顺序.该迭代顺序可以是插入顺序或者是访问顺序.通过维护一个 ...

  2. JDK源码学习笔记——String

    1.学习jdk源码,从以下几个方面入手: 类定义(继承,实现接口等) 全局变量 方法 内部类 2.hashCode private int hash; public int hashCode() { ...

  3. JDK源码学习笔记——Integer

    一.类定义 public final class Integer extends Number implements Comparable<Integer> 二.属性 private fi ...

  4. JDK源码学习笔记——Enum枚举使用及原理

    一.为什么使用枚举 什么时候应该使用枚举呢?每当需要一组固定的常量的时候,如一周的天数.一年四季等.或者是在我们编译前就知道其包含的所有值的集合. 利用 public final static 完全可 ...

  5. JDK源码学习笔记——Object

    一.源码解析 public class Object { /** * 一个本地方法,具体是用C(C++)在DLL中实现的,然后通过JNI调用 */ private static native void ...

  6. 【jdk源码学习】HashMap

    package com.emsn.crazyjdk.java.util; /** * “人”类,重写了equals和hashcode方法...,以id来区分不同的人,你懂的... * * @autho ...

  7. jdk源码阅读笔记-HashMap

    文章出处:[noblogs-it技术博客网站]的博客:jdk1.8源码分析 在Java语言中使用的最多的数据结构大概右两种,第一种是数组,比如Array,ArrayList,第二种链表,比如Array ...

  8. JDK源码学习笔记——HashSet LinkedHashSet TreeSet

    你一定听说过HashSet就是通过HashMap实现的 相信我,翻一翻HashSet的源码,秒懂!! 其实很多东西,只是没有静下心来看,只要去看,说不定一下子就明白了…… HashSet 两个属性: ...

  9. JDK源码学习笔记——TreeMap及红黑树

    找了几个分析比较到位的,不再重复写了…… Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例 [Java集合源码剖析]TreeMap源码剖析 java源码分析之TreeMap基础篇 ...

随机推荐

  1. 生成应用的快捷方式action,权限

    action:"com.android.launcher.action.INSTALL_SHORTCUT" 权限:com.android.launcher.permission.I ...

  2. CPU架构及并发编程基础(一)

    一.intel cpu发展计划tick-tock Tick-Tock是Intel发展微处理器芯片设计制造业务的一种战略模式.Intel指出,每一次处理器微架构的更新和每一次芯片制程的更新遵循“Tick ...

  3. 看jquery3.3.1学js类型判断的技巧

    需要预习:call , typeof, js数据类型 1. isFunction中typeof的不靠谱 源码: var isFunction = function isFunction( obj ) ...

  4. SqlServer开启CLR使用(C#)DLL实现实时Socket通知

    --1.默认情况下,SQL Server中的CLR是关闭的,所以我们需要执行如下命令打开CLR: reconfigure GO -- DROP FUNCTION dbo.fnScoketSend -- ...

  5. [New learn] 网络基础-apache本地服务搭建(支持php)

    1.简介 无网不利,无网不胜.对于移动应用来说离开网络那和咸鱼有什么分别?所以对于开发者来说更要学习好网络开发的技术. 2.搭建apache本地服务器 1.在finder中显示影藏的用户文件夹 fin ...

  6. servlet为什么要配置web.xml

    (1).为Servlet命名:  <servlet>  <servlet-name>servlet1</servlet-name> <- 这是用于,在serv ...

  7. 讲IOC非常好的一篇文章--初步弄懂DI

    http://jinnianshilongnian.iteye.com/blog/1413846 http://jinnianshilongnian.iteye.com/blog/pdf 之后又看了类 ...

  8. DuplicateHandle

    功能:将一个进程内的伪句柄,转化为可以用来进程间通信的实句柄 BOOL DuplicateHandle(  HANDLE hSourceProcessHandle,  HANDLE hSourceHa ...

  9. 解决报错:Access denied for user 'root'@'xxx' ,MySQL不允许从远程访问的方法 .

    原文地址:传送门 例如,你想myuser使用mypassword从任何主机连接到mysql服务器的话. GRANT ALL PRIVILEGES ON *.* TO 'myuser'@'%' IDEN ...

  10. Sql Server递归查询(转)

    有如下数据表 假如我们要查询ID为003的数据的所有子节点我们可以使用CTE 递归查询完成... if OBJECT_ID('tb','N') is not null drop table tb; c ...