锁定老帖子 主题:HashMap深度分析
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2010-09-05
楼主的分析确实很到位
|
|
返回顶楼 | |
发表时间:2010-09-05
最近又有好多人发表对hashmap的研究啊。。
|
|
返回顶楼 | |
发表时间:2010-09-06
死锁是扩容操作与put或get方法并发引起的,看源码: 引用 void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e;//切换2 e = next; } while (e != null); } } } //get() for (Entry<K,V> e = table[indexFor(hash, table.length)];//切换1 e != null; e = e.next) table初始状态: 假设执行get的线程1在e = table[indexFor(hash, table.length)]后进行“切换1”,切换到线程2的put方法,而且进行了扩容。此时table和newTable的状态是: 线程2在newTable[i] = e后进行“切换2”,继续执行线程1的get,在for循环里e.next==e,永远不为空,若 if (e.hash == hash && ((k = e.key) == key || key.equals(k)))不满足,则进入了死循环。 |
|
返回顶楼 | |
发表时间:2010-09-06
楼上解释得很好
|
|
返回顶楼 | |
发表时间:2010-09-06
最后修改:2010-09-06
cash-007 写道 resize后
k.hashCode()) 定值 hash(k.hashCode());定值 indexFor(hash, table.length);因为table.length 翻倍了,所以计算出的位置肯定会有变化。但是hash值是不会变的。 比如 扩容前 length×2后 length 4 8 length-1 11(3) 111(7) hashcode 10(2) 10(2) i值 10(2) 110(6) 所以扩容前在table[2]位置,扩容后就在table[6] 扩容前 length×2后 length 4 8 length-1 11(3) 111(7) hashcode 10(2) 010(2) i值 10(2) 010(2) 我写了一个例子,观察了一下,确实i的值没有变化, 另外,我发现jdk6中,resize似乎有优化,比如我插入10000个对象,用new HashMap()和new HashMap(10000),运行的时间基本一致。 还有补充一下,HashMap的容量永远是2的N次方(当然肯定是偶数),这样的作的目的是提高Map的效率,因为当length - 1的二进制总是111...111,这样计算indexFor时得到的结果永远是小于Map的长度的。 |
|
返回顶楼 | |
发表时间:2010-09-06
netingcn 写道 cash-007 写道 resize后
k.hashCode()) 定值 hash(k.hashCode());定值 indexFor(hash, table.length);因为table.length 翻倍了,所以计算出的位置肯定会有变化。但是hash值是不会变的。 比如 扩容前 length×2后 length 4 8 length-1 11(3) 111(7) hashcode 10(2) 10(2) i值 10(2) 110(6) 所以扩容前在table[2]位置,扩容后就在table[6] 扩容前 length×2后 length 4 8 length-1 11(3) 111(7) hashcode 10(2) 010(2) i值 10(2) 010(2) 我写了一个例子,观察了一下,确实i的值没有变化, 另外,我发现jdk6中,resize似乎有优化,比如我插入10000个对象,用new HashMap()和new HashMap(10000),运行的时间基本一致。 还有补充一下,HashMap的容量永远是2的N次方(当然肯定是偶数),这样的作的目的是提高Map的效率,因为当length - 1的二进制总是111...111,这样计算indexFor时得到的结果永远是小于Map的长度的。 你说对了结果,但是没有说对原因,“HashMap的容量永远是2的N次方(当然肯定是偶数),这样的作的目的是提高Map的效率”,的确如此,“因为当length - 1的二进制总是111...111,这样计算indexFor时得到的结果永远是小于Map的长度的”,你说的这个是错误的,要知道,一个大数字和一个小数字按位与运算,结果永远不可能大于大数字,HashMap里的这种做法是为了使数据能够更均匀的分布到数组里,我在帖子里已经详细讲过了。 |
|
返回顶楼 | |
发表时间:2010-09-06
研究的好深啊。
|
|
返回顶楼 | |
发表时间:2010-09-06
收藏一下回家看,这是在读源码丫
|
|
返回顶楼 | |
发表时间:2010-09-06
楼主精神可嘉 俺当时培训的时候跟着源码写了一遍
文章较长 看起来还是觉得停轻松 毕竟亲自写过 路漫漫其修远兮 吾将上下而求索 |
|
返回顶楼 | |
发表时间:2010-09-07
ch_space 写道 死锁是扩容操作与put或get方法并发引起的,看源码: 引用 void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e;//切换2 e = next; } while (e != null); } } } //get() for (Entry<K,V> e = table[indexFor(hash, table.length)];//切换1 e != null; e = e.next) table初始状态: 假设执行get的线程1在e = table[indexFor(hash, table.length)]后进行“切换1”,切换到线程2的put方法,而且进行了扩容。此时table和newTable的状态是: 线程2在newTable[i] = e后进行“切换2”,继续执行线程1的get,在for循环里e.next==e,永远不为空,若 if (e.hash == hash && ((k = e.key) == key || key.equals(k)))不满足,则进入了死循环。 这个就是想问的答案 |
|
返回顶楼 | |