在看网上HashMap的resize()设计时,提到尾部遍历。
 
JDK1.7的HashMap在实现resize()时,新table[]的列表采用LIFO方式,即队头插入。这样做的目的是:避免尾部遍历。
 
避免尾部遍历是为了避免在新列表插入数据时,遍历到队尾的位置。因为,直接插入的效率更高。

对resize()的设计来说,本来就是要创建一个新的table,列表的顺序不是很重要。
但如果要确保插入队尾,还得遍历出链表的队尾位置,然后插入,是一种多余的损耗。
 
直接采用队头插入,会使得链表数据倒序
例如原来顺序是:
 10  20  30  40
插入顺序如下
  10
   20  10
   30 20 10
   40 30 20 10
 
 
存在问题:
采用队头插入的方式,导致了HashMap在“多线程环境下”的死循环问题:http://www.cnblogs.com/chengdabelief/p/7419776.html
 
 
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;
}

HashMap的尾部遍历问题--Tail Traversing的更多相关文章

  1. HashMap的resize方法中尾部遍历出现死循环问题 Tail Traversing (多线程)

    一.背景介绍: 在看HashMap源码是看到了resize()的源代码,当时发现在将old链表中引用数据复制到新的链表中时,发现复制过程中时,源码是进行了反序,此时是允许反序存储的,同时这样设计的效率 ...

  2. Java中关于HashMap的元素遍历的顺序问题

    Java中关于HashMap的元素遍历的顺序问题 今天在使用如下的方式遍历HashMap里面的元素时 1 for (Entry<String, String> entry : hashMa ...

  3. HashMap 集合的遍历

    HashMap 集合的遍历: 两种方式遍历HashMap: //集合hashMap的遍历: //方式一: @Test public void testMethod1(){ HashMap<Str ...

  4. hashmap 的边遍历边存储

    PS: Hashmap 的一边遍历边存储,可解决例如两数之和. 无重复最长子串问题等,代码为cpp格式. 以无重复最长子串为例. class Solution { public: int length ...

  5. HashMap两种遍历方式的深入研究

    转自:http://swiftlet.net/archives/1259 HashMap的遍历有两种方式,如下所示:第一种利用entrySet的方式:   1 2 3 4 5 6 7 Map map ...

  6. hashmap两种遍历方法

    第一种:使用entryset来进行遍历 Map map=new HashMap(); Iterator iter=map.entrySet().iterator(); while(iter.hasNe ...

  7. HashMap两种遍历数据的方式

    HashMap的遍历有两种方式,一种是entrySet的方式,另外一种是keySet的方式. 第一种利用entrySet的方式: Map map = new HashMap(); Iterator i ...

  8. Java HashMap 如何正确遍历并删除元素

    (一)HashMap的遍历 HashMap的遍历主要有两种方式: 第一种采用的是foreach模式,适用于不需要修改HashMap内元素的遍历,只需要获取元素的键/值的情况. HashMap<K ...

  9. java 遍历方法 及 数组,ArrayList,HashMap,HashSet的遍历

    一,遍历方法的实现原理 1.传统的for循环遍历,基于计数器的: 遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止.主要就是需要按元素的位置来读取元素. ...

随机推荐

  1. 使用NamedParameterJdbcTemplate

    [在JDBC模板中使用具名参数] 1.在经典的JDBC用法中,SQL参数使用占位符?表示,并且受到位置的限制.定为参数的问题在于,一旦参数的顺序发生变化,就必须改变参数绑定. 2.在Spring JD ...

  2. RequestMapping_请求参数&请求头

    params和headers支持简单的表达式: --param1:表示请求必须包含名为param1的请求参数. --!param1:表示请求不能包含名为param1的请求参数. --param1 != ...

  3. 【Codeforces 1129A】Toy Train

    [链接] 我是链接,点我呀:) [题意] 火车从1,2,3...n->1的方式绕圈走.(即每次从i走到i+1) 有一些点有货物需要装载,但是每个点只能装上去一个货物. 每个货物都有目标点卸货点( ...

  4. ACM数论常用知识完全解读

    此版本纯属扯淡....... 一个一个来起.

  5. JS动态添加div,然后在div中添加元素

    需求: 组织部中有个这样的需求,根据年份动态显示该年份下的定性指标! 我的做法: 先是放一个空的div,让后根据指标的数据,动态的往div中添加元素. 代码: 空的div,存放定性指标 <div ...

  6. PostGIS学习相关术语

    POI 兴趣点(英语:point of interest,通常缩写成POI)乃是电子地图上的某个地标.景点,用以标示出该地所代表的政府部门.各行各业之商业机构(加油站.百货公司.超市.餐厅.酒店.便利 ...

  7. 为什么Linux下的环境变量要用大写而不是小写

    境变量的名称通常用大写字母来定义.实际上用小写字母来定义环境变量也不会报错,只是习惯上都是用大写字母来表示的. 首先说明一下,在Windows下是不区分大小写的,所以在Windows下怎么写都能获取到 ...

  8. JavaScript使用正則表達式

    2.0 简单介绍 正則表達式是能够用来查找与给定模式匹配的文本的搜索模式.比如,在上一章中,我们在一个较长的字符串中查找子字符串Cookbook: var testValue = "This ...

  9. [dfs] UVALive 3667 Ruler

    题目链接: option=com_onlinejudge&Itemid=8&page=show_problem&problem=1668">https://ic ...

  10. LeetCode 557. Reverse Words in a String III (反转字符串中的单词 III)

    Given a string, you need to reverse the order of characters in each word within a sentence while sti ...