1. put()
1 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 2 boolean evict) { 3 HashMap.Node<K,V>[] tab;//指向存储数组的引用 4 HashMap.Node<K,V> p; 5 int n, i;//n是数组长度,i是插入值的存储下标 6 //判断存储数组是否为空,即数组是否初始化 7 if ((tab = table) == null || (n = tab.length) == 0) 8 n = (tab = resize()).length; 9 //计算出存储下标,并将数组中下标与插入值位置相同的赋给p 10 if ((p = tab[i = (n - 1) & hash]) == null) 11 //如果p为null则表示插入值的插入位置没有值,则直接插入 12 tab[i] = newNode(hash, key, value, null); 13 //p!=null 14 else { 15 HashMap.Node<K,V> e; 16 K k; 17 //判断p的key是否与插入值的key相等,如果相等,则不需要插入 18 if (p.hash == hash && 19 ((k = p.key) == key || (key != null && key.equals(k)))) 20 e = p; 21 //如果p是红黑树节点 22 else if (p instanceof HashMap.TreeNode) 23 //直接将值插入到红黑树中,在红黑树的插入中同样会进行key是否存在的判断,如果有则返回那个节点,否则返回null 24 e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 25 //p是链表节点 26 else { 27 //遍历链表 28 for (int binCount = 0; ; ++binCount) { 29 //如果链表到头了,则将当前值插入到链表中 30 if ((e = p.next) == null) { 31 //插入 32 p.next = newNode(hash, key, value, null); 33 //判断链表的长度是否达到变成红黑树的长度 34 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 35 //变成红黑树 36 treeifyBin(tab, hash); 37 //如果从这里break出去,e的值就为null 38 break; 39 } 40 //如果链表中存在key与插入值的key相同的节点,break 41 if (e.hash == hash && 42 ((k = e.key) == key || (key != null && key.equals(k)))) 43 break; 44 p = e; 45 } 46 } 47 //e!=null表示map中存在key与插入值的key相同,则更新值就行 48 if (e != null) { // existing mapping for key 49 V oldValue = e.value; 50 if (!onlyIfAbsent || oldValue == null) 51 e.value = value; 52 //对于HashMap来说是空函数,没有意义,LinkedHashMap中才有用 53 afterNodeAccess(e); 54 return oldValue; 55 } 56 } 57 //modCount是数组实际插入次数(根据代码来看,如果没有插入,只是更新的话是不会增加的) 58 ++modCount; 59 //size是map中的元素个数(包括链表,红黑树以及数组上的元素),thresold是扩容阈值,如果达到这个值则扩容 60 if (++size > threshold) 61 resize(); 62 //对于HashMap来说是空函数,没有意义,LinkedHashMap中才有用 63 afterNodeInsertion(evict); 64 return null; 65 }
2. 扩容
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) { // table已初始化 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; // 容量翻倍 14 } 15 else if (oldThr > 0) // table未初始化且已设置阈值,初始容量设为阈值 16 newCap = oldThr; 17 else { // table未初始化且未设置阈值 18 newCap = DEFAULT_INITIAL_CAPACITY; 19 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 20 } 21 if (newThr == 0) { // 1.table未初始化且已设置阈值 2.table已初始化但扩容后容量超过限制 3.table已初始化但原table容量小于默认值 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; // 更新threshold 27 @SuppressWarnings({"rawtypes","unchecked"}) 28 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 29 table = newTab; // 更新table 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; // 从原table中删除正在移动的元素 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 { // 链表节点 40 // 如果对于这里涉及到的低位、高位概念不懂,在我的另一篇博文ConcurrentHashMap源码解析(JDK8)中有介绍 41 Node<K,V> loHead = null, loTail = null; // 用于记录低位头结点与尾节点的变量 42 Node<K,V> hiHead = null, hiTail = null;// 用于记录高位头结点与尾节点的变量 43 Node<K,V> next; 44 do { // 遍历链表,遍历出来的两条链表保持原有的顺序 45 next = e.next; 46 if ((e.hash & oldCap) == 0) { // 表示该节点是低位节点 47 if (loTail == null) 48 loHead = e; 49 else 50 loTail.next = e; 51 loTail = e; 52 } 53 else { // 表示该节点是高位节点 54 if (hiTail == null) 55 hiHead = e; 56 else 57 hiTail.next = e; 58 hiTail = e; 59 } 60 } while ((e = next) != null); 61 // 将两条链表插入到新table中 62 if (loTail != null) { 63 loTail.next = null; 64 newTab[j] = loHead; 65 } 66 if (hiTail != null) { 67 hiTail.next = null; 68 newTab[j + oldCap] = hiHead; 69 } 70 } 71 } 72 } 73 } 74 return newTab; 75 }
虽然JDK8没有了扩容时死循环的问题,但是依然是线程不安全的,在多线程环境中请使用ConcurrentHashMap。