在看网上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. hdu 3062 2-sat

    #include<stdio.h> #include<string.h> #define N 2100 struct node { int u,v,next; }bian[N* ...

  2. linux安装java mysql tomcat 环境

    安装jdk: 1. 查看系统版本 uname -a 2.查看操作系统 cat  /proc/version 3.上传安装文件到指定目录   tar -zxvf jdkxxx. 4.修改配置文件  vi ...

  3. 在代码动态设置RelativeLayout的属性,比如layout_below

    ( (RelativeLayout.LayoutParams)holder.ivLvDivider.getLayoutParams()).addRule(RelativeLayout.BELOW, R ...

  4. Java使用JNative调用DLL库

    编写测试DLL文件,源文件参照JNA调用用的DLL文件.地址:http://www.cnblogs.com/vevy/p/9076941.html (很重要)右键项目 --> 属性 --> ...

  5. Angularjs中添加ckEditor插件

    使用方法看注释.主要解决帮上ngModel的问题 angular.module('newApp') .directive('ckeEditor', function() { return { /* F ...

  6. 进销存管理系统, 刚学C++

    各位大神们.有什么补充的能够评论一下吗? #include<iostream> #include<string> using namespace std; int G=0;// ...

  7. 从2月14号開始,上传AppStore会碰到:Failed to locate or generate matching signing assets

    从2月14号開始,上传AppStore时可能会碰到这个问题: Failed to locate or generate matching signing assets Xcode attempted ...

  8. 模块化开发(三)---通过node.js学习模块化开发

    由于改文章有点大,部分代码格式有点问题,编辑之后博客园莫名其妙推出,有问题可以留言沟通.   什么是Node? 它是一个在浏览器之外可以解析和执行javascript代码的运行环 境,或者说是一个运行 ...

  9. 删除svn

    去掉文件下所有的.svn - CSDN博客  https://blog.csdn.net/arui_email/article/details/9055645 FOR /r  D:\testsvn\p ...

  10. ALSA声卡驱动中的DAPM详解之三:如何定义各种widget

    上一节中,介绍了DAPM框架中几个重要的数据结构:snd_soc_dapm_widget,snd_soc_dapm_path,snd_soc_dapm_route.其中snd_soc_dapm_pat ...