HashMap源码分析
贡献博客阅读
1:modCount解释:https://www.cnblogs.com/wl889490/p/12773805.html
2:fast-fail机制:https://www.jianshu.com/p/b24f77bbfeda?ivk_sa=1024320u
3:hashmap中高位低位讲解:https://blog.csdn.net/u010425839/article/details/106620440
4:afterNodeInsertion(evict)方法作用:https://segmentfault.com/q/1010000009323139
HashMap方法初始化
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); }
主要作用:
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
如果有加载因子,则复制给loadFactor,如果没有加载因子,默认的加载因子是0.75
临界值大小:这里的临界值大小,是获取的初始化容量的2的最小公倍数,便于后面使用
注意:这里赋值的就是容器的大小,具体的临界值,在第一次赋值的时候,拿容器大小乘以加载因子
tableSizeFor方法如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
作用:取到最小公倍数,便于后面得取模运算,因为2得最小公倍数,一定是类似:0001 0000得这种格式
2:HashMap的put方法
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
主要看 hash(key)方法的作用:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
作用:让key的hash值得高位也能参与计算:
比如: key.hash = 0001 0111 0101 1000
则:h >>> 16位得到:0000 0000 0001 0111
它两做异或运算得时候,原高位数不变,低位因为是异或,只有不同才会为1,所以大部分高位得也保留了,低位得基本也保留,偶尔几个相同得变为1,
从而最大可能得让高位参与到运算中来
再往下看putval()方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 2 boolean evict) { 3 Node<K,V>[] tab; Node<K,V> p; int n, i; 4 if ((tab = table) == null || (n = tab.length) == 0) 5 n = (tab = resize()).length; 6 if ((p = tab[i = (n - 1) & hash]) == null) 7 tab[i] = newNode(hash, key, value, null); 8 else { 9 Node<K,V> e; K k; 10 if (p.hash == hash && 11 ((k = p.key) == key || (key != null && key.equals(k)))) 12 e = p; 13 else if (p instanceof TreeNode) 14 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 15 else { 16 for (int binCount = 0; ; ++binCount) { 17 if ((e = p.next) == null) { 18 p.next = newNode(hash, key, value, null); 19 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 20 treeifyBin(tab, hash); 21 break; 22 } 23 if (e.hash == hash && 24 ((k = e.key) == key || (key != null && key.equals(k)))) 25 break; 26 p = e; 27 } 28 } 29 if (e != null) { // existing mapping for key 30 V oldValue = e.value; 31 if (!onlyIfAbsent || oldValue == null) 32 e.value = value; 33 afterNodeAccess(e); 34 return oldValue; 35 } 36 } 37 ++modCount; 38 if (++size > threshold) 39 resize(); 40 afterNodeInsertion(evict); 41 return null; 42 }
第4行,判断tab是否为空,或者长度为0,对于首次进来,肯定是的,这时候就要进行tab的初始化
resize()方法如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 final Node<K,V>[] resize() { 2 Node<K,V>[] oldTab = table; 3 int oldCap = (oldTab == null) ? 0 : oldTab.length; 4 int oldThr = threshold; 5 int newCap, newThr = 0; 6 if (oldCap > 0) { 7 if (oldCap >= MAXIMUM_CAPACITY) { 8 threshold = Integer.MAX_VALUE; 9 return oldTab; 10 } 11 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 12 oldCap >= DEFAULT_INITIAL_CAPACITY) 13 newThr = oldThr << 1; // double threshold 14 } 15 else if (oldThr > 0) // initial capacity was placed in threshold 16 newCap = oldThr; 17 else { // zero initial threshold signifies using defaults 18 newCap = DEFAULT_INITIAL_CAPACITY; 19 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 20 } 21 if (newThr == 0) { 22 float ft = (float)newCap * loadFactor; 23 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 24 (int)ft : Integer.MAX_VALUE); 25 } 26 threshold = newThr; 27 @SuppressWarnings({"rawtypes","unchecked"}) 28 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 29 table = newTab; 30 if (oldTab != null) { 31 for (int j = 0; j < oldCap; ++j) { 32 Node<K,V> e; 33 if ((e = oldTab[j]) != null) { 34 oldTab[j] = null; 35 if (e.next == null) 36 newTab[e.hash & (newCap - 1)] = e; 37 else if (e instanceof TreeNode) 38 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 39 else { // preserve order 40 Node<K,V> loHead = null, loTail = null; 41 Node<K,V> hiHead = null, hiTail = null; 42 Node<K,V> next; 43 do { 44 next = e.next; 45 if ((e.hash & oldCap) == 0) { 46 if (loTail == null) 47 loHead = e; 48 else 49 loTail.next = e; 50 loTail = e; 51 } 52 else { 53 if (hiTail == null) 54 hiHead = e; 55 else 56 hiTail.next = e; 57 hiTail = e; 58 } 59 } while ((e = next) != null); 60 if (loTail != null) { 61 loTail.next = null; 62 newTab[j] = loHead; 63 } 64 if (hiTail != null) { 65 hiTail.next = null; 66 newTab[j + oldCap] = hiHead; 67 } 68 } 69 } 70 } 71 } 72 return newTab; 73 }
第一次进来的话只会走到 第16行,第21行处
作用:
1:16行,将newCap赋值为threadhold,上面说过就是2的最小公倍数,也是容器的初始化大小
2:21行,容器乘以加载因子(0.75),进行容器的临界值大小的计算
3:返回给tab数组
resize()方法之后:
threadhold 已经初始化,为容器大小乘以加载因子
table已经被赋值为初始化好的tab
回到上面putVal()方法:
代码执行到第6行,判断当前的tab下标的元素是否为空,当然第一次进来肯定为空,这时候执行到第7行,将当前存放的key和value存放到
当前所属的下标下面,代码执行完毕
第6行详解:if ((p = tab[i = (n - 1) & hash]) == null)
n-1:为啥要减去1,因为n是容器的容量,而容器的容量为2的最小公倍数,减去一个可以快速取余,且不会超过容器的容量。假设容量为16举例
比如:16的二进制为:0001 0000 16-1=15的二进制为:0000 1111
hash 的值不管为多少比如:
hash: 0011 0101 0011 0001
15二进制:0000 0000 0000 1111
进行求与运算,不管hash的值多大,高位全部为0,只有低位的四位参与计算,相同才会保留为1,也就是hash % n 求余
接着往下:
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
这几行代码的作用:
1:modCount是记录容器改变的次数,只要是添加,就会自增1,作用:防止容器使用迭代器进行遍历的时候,对容器进行添加。
其实就是HashMap是线程不安全导致的,
使用的fail-fast机制,具体的fial-fast的机制可以看上面的博客,或者下面说明。
2:后面if代码的判断是否超过临界值了,超过了就进行扩容
3:afterNodeInsertion(evict); 这个博客解释的很清楚,不属于hashMap源码范围:https://segmentfault.com/q/1010000009323139
接着扩容来讲:
当容器的大小超过了临界值的时候,就要进行扩容了,扩容调用的方法还是resize()方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 final Node<K,V>[] resize() { 2 Node<K,V>[] oldTab = table; 3 int oldCap = (oldTab == null) ? 0 : oldTab.length; 4 int oldThr = threshold; 5 int newCap, newThr = 0; 6 if (oldCap > 0) { 7 if (oldCap >= MAXIMUM_CAPACITY) { 8 threshold = Integer.MAX_VALUE; 9 return oldTab; 10 } 11 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 12 oldCap >= DEFAULT_INITIAL_CAPACITY) 13 newThr = oldThr << 1; // double threshold 14 } 15 else if (oldThr > 0) // initial capacity was placed in threshold 16 newCap = oldThr; 17 else { // zero initial threshold signifies using defaults 18 newCap = DEFAULT_INITIAL_CAPACITY; 19 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 20 } 21 if (newThr == 0) { 22 float ft = (float)newCap * loadFactor; 23 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 24 (int)ft : Integer.MAX_VALUE); 25 } 26 threshold = newThr; 27 @SuppressWarnings({"rawtypes","unchecked"}) 28 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 29 table = newTab; 30 if (oldTab != null) { 31 for (int j = 0; j < oldCap; ++j) { 32 Node<K,V> e; 33 if ((e = oldTab[j]) != null) { 34 oldTab[j] = null; 35 if (e.next == null) 36 newTab[e.hash & (newCap - 1)] = e; 37 else if (e instanceof TreeNode) 38 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 39 else { // preserve order 40 Node<K,V> loHead = null, loTail = null; 41 Node<K,V> hiHead = null, hiTail = null; 42 Node<K,V> next; 43 do { 44 next = e.next; 45 if ((e.hash & oldCap) == 0) { 46 if (loTail == null) 47 loHead = e; 48 else 49 loTail.next = e; 50 loTail = e; 51 } 52 else { 53 if (hiTail == null) 54 hiHead = e; 55 else 56 hiTail.next = e; 57 hiTail = e; 58 } 59 } while ((e = next) != null); 60 if (loTail != null) { 61 loTail.next = null; 62 newTab[j] = loHead; 63 } 64 if (hiTail != null) { 65 hiTail.next = null; 66 newTab[j + oldCap] = hiHead; 67 } 68 } 69 } 70 } 71 } 72 return newTab; 73 }
第:2,3,4行执行完成之后,oldTab就是当前的容器,oldCap 和oldThr就是当前容器的大小和临界值
第:11 行肯定都是满足的,这时候就需要进行扩容了,容器的大小和临界值的大小都乘以2,代码执行完成之后如下:
newCap = oldCap * 2 newThr = oldThr * 2
第:26 ,28,29行,把新的临界值赋值给临界值变量,初始化新的容器,将新的容器赋值给table变量
重点来了:
代码执行到31行,进行老容器变量的copy,开始进行遍历
1:35行,如果老容器的节点不为空,且没有链表,就一个,那就拿当前的hash 值和新容器进行求余,存放到新容器的节点上
2:39行,如果老容器的节点不为空,且是链表,这时候所有的链表上的节点都要重新求余,再存放
说明:
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
这四个节点的含义:lo 表示低位 是low的缩写 而 hi是高位,high的缩写 ,可以参看上面的博客:https://blog.csdn.net/u010425839/article/details/106620440
什么是高位,什么是低位,怎么判断的?
结合代码第45行:if ((e.hash & oldCap) == 0)
e.hash & oldCap == 0 就是低位,不等于0就是高位,为啥这样说。 当oldCap等于16举例子
比如: 16的二进制为:0001 0000
其他数的二进制,不超过16的二进制:0000 1111这是最大的,这两个数进行“与运算”还是0,其实不管怎么算,只要不超过16,低位不管是0还是1最后都是0,
所以超过16的算高位,小于16的算低位。
第二:小于16的,那么在新容器里面,还是原来的位置,很好立即,求余肯定保持不变,而大于16的求余就是 16 加上和 16求余的余数
比如:2 % 16 ==2 把容器换成32 2 % 32 == 2
比如:18 % 16 == 2 把容器换成32 18 % 32 == 18 也就是 18 % 16 + 16 就是18和老容器求余,加上老容器大小
所以代码大概意思就是:
第一:遍历某个节点上的链表
第二:判断该节点的hash值是高位,还是低位 。 低位放在低位的链表 高位放在高位的链表
第三:低位的在新容器位置不变,直接放在新容器的原位置节点,高位链表,放在老容器大小 + 原来的位置也就是上面解释的内容
什么是fast-fail机制:
我们知道 java.util.HashMap 不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出
ConcurrentModificationException,
这就是所谓fail-fast策略。这一策略在源码中的实现是通过 modCount 域,modCount 顾名思义就是修改次数,对HashMap 内容的修改
都将增加这个值,
那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,
如果不相等就表示已经有其他线程修改了 Map:注意到 modCount 声明为 volatile,保证线程之间修改的可见性。