首页 > 其他分享 >HashMap的尾部遍历问题 (Tail Traversing)

HashMap的尾部遍历问题 (Tail Traversing)

时间:2022-10-17 17:00:27浏览次数:43  
标签:Node newCap HashMap else next Tail Traversing null oldCap


JDK1.7的HashMap在实现resize()时,新table[]的列表采用LIFO方式,即队头插入。
这样做的目的是:避免尾部遍历。

避免尾部遍历是为了避免在新列表插入数据时,遍历到队尾的位置。因为,直接插入的效率更高。

对resize()的设计来说,本来就是要创建一个新的table,列表的顺序不是很重要。但如果要确保插入队尾,还得遍历出链表的队尾位置,然后插入,是一种多余的损耗。
直接采用队头插入,会使得链表数据倒序。

例如原来顺序是:

 10  20  30  40

插入顺序如下

   10
20 10
30 20 10
40 30 20 10

存在问题:

JDK1.8的优化

JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,JDK1.8不会倒置,通过增加tail指针,既避免了死循环问题(让数据直接插入到队尾),又避免了尾部遍历。代码如下:

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);
}
// 计算新的resize上限
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) {
// 把每个bucket都移动到新的buckets中
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;
}
// 原索引+oldCap
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 原索引放到bucket里
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 原索引+oldCap放到bucket里
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}

标签:Node,newCap,HashMap,else,next,Tail,Traversing,null,oldCap
From: https://blog.51cto.com/u_15236724/5763600

相关文章

  • ConcurrentHashMap源码,看我这篇就够了
    思考:HashTable是线程安全的,为什么不推荐使用?HashTable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,即每次锁住整张表让线程独占,相当于所有线程进......
  • ConcurrentHashMap源码,看我这篇就够了
    持续创作,加速成长!这是我参与「掘金日新计划·10月更文挑战」的第5天,点击查看活动详情思考:HashTable是线程安全的,为什么不推荐使用?HashTable是一个线程安全的类,它使用s......
  • Tailwind CSS如何编码像素完美设计
    从2.1版本开始,我们可以启用JIT并使用如下所示的任意样式:mb-[278px]......
  • hashmap组成原理及调用时机
    整个HashMap中最重要的点有四个:初始化,数据寻址-hash方法,数据存储-put方法,扩容-resize方法,只要理解了这四个点的原理和调用时机,也就理解了整个HashMap的设计。 如果有疑......
  • 获取成本中心层级BAPI_COSTCENTERGROUP_GETDETAIL
    T-CODE:OKEON可以查看成本中心层级如果你查找对应的表,会查找到  SETHEADER和对应的SETHEADERT文本表对照数据可能发下问题应该还有别的判断条件,其他字段这里也不继续看......
  • 二周第二次课(3月27日)2.10 环境变量PATH 2.11 cp命令 2.12 mv命令 2.13 文档查看ca
    2.10 环境变量PATHecho$PATH//打印当前的环境变量PATH=$PATH:路径//定义环境变量(在源环境变量的基础上增加)PATH=路径//修改环境变量which查找某个命令的绝对路径,也可以......
  • HashMap可以设置初始化容量大小吗?如果设置为20是多少?
    这个是在我在面试中遇到的问题,是关于HashMap;个人学习笔记记录。HashMap设置初始化容量20的具体流程:答:是可以设置初始容量大小,设置为20,容量为32,2的n次方。1、设置初始化......
  • HashMap index 计算以及 map 扩容旧值如何处理
    首先index如何计算   我们都知道添加一个Entry进Map其实就是添加一个不可重复的key进入散链表中,    在计算的时候首先会获取到index的hash值而ha......
  • Java HashMap
    importjava.util.HashMap;/***JavaHashMap*HashMap是一个散列表,它存储的内容是键值对(key-value)映射。**HashMap实现了Map接口,根据键的HashCode值存储数据,......
  • HashMap实现原理及源码分析
    哈希表(hashtable)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出现......