JDK8 HashMap扩容机制分析详解
HashMap
HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一,是非线程安全的。可以存储null值,但是只有一个key可以为null,有多个值可以为null。
JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, HashMap 总是使用 2 的幂作为哈希表的大小。
put方法
HashMap只提供了put来添加元素,put内部调用putVal方法
对putVal 的分析如下
如果定位到的数组没有元素,就直接插入;
如果定位到的数组有元素,比较key,如果key相同就直接覆盖,如果相同,判断p是否是一个树节点
- 如果是,调用树节点的插入方法将元素插入;
- 如果不是,遍历链表,将元素插入到链表尾部;
说明:直接覆盖之后会return,不会有后续操作;链表长度大于阈值8并且数组长度大于64的时候才会转换红黑树,否则只是扩容数组。
put方法示例
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //table未初始化或者长度为0,进行扩容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //(n - 1) & hash 确定元素存放在哪个桶中,桶为空, //新生成结点放入桶中(此时,这个结点是放在数组中) if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //桶中已经存在元素 else { Node<K,V> e; K k; //key相等,hash相等 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //将元素赋值给e e = p; //hash值不相等,即key不相等,并且该节点是红黑树节点 else if (p instanceof TreeNode) //放入树中 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //链表节点 else { //在链表末尾插入节点 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //节点数量达到阈值8,执行treeifyBin方法 //此方法会根据HashMap的数组来决定是否要转换为红黑树 //数组长度大于等于64才会转换为红黑树,否则只会扩容数组 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); //跳出循环 break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //桶中有key值和hash值于插入节点相等的节点 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); //覆盖以后返回旧值 return oldValue; } } ++modCount; //插入完成后实际大小大于阈值,需要扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
resize方法
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; //容量超过最大值就不再扩容 if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //没超过最大值扩容到原来的2倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { //扩容完成后,重新进行hash分配,写入数据 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
以上就是JDK8 HashMap扩容机制分析详解的详细内容,更多关于JDK8 HashMap扩容机制的资料请关注脚本之家其它相关文章!
相关文章
聊聊spring @Transactional 事务无法使用的可能原因
这篇文章主要介绍了spring @Transactional 事务无法使用的可能原因,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-07-07springmvc后台基于@ModelAttribute获取表单提交的数据
这篇文章主要介绍了springmvc后台基于@ModelAttribute获取表单提交的数据,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下2019-10-10
最新评论