论坛首页 Java企业应用论坛

HashMap深度分析

浏览 52210 次
该帖已经被评为精华帖
作者 正文
   发表时间:2010-09-05  
楼主的分析确实很到位
0 请登录后投票
   发表时间:2010-09-05  
最近又有好多人发表对hashmap的研究啊。。
0 请登录后投票
   发表时间: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)))
不满足,则进入了死循环。
  • 大小: 651 Bytes
  • 大小: 2.6 KB
0 请登录后投票
   发表时间:2010-09-06  
楼上解释得很好
0 请登录后投票
   发表时间: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的长度的。
0 请登录后投票
   发表时间: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里的这种做法是为了使数据能够更均匀的分布到数组里,我在帖子里已经详细讲过了。
0 请登录后投票
   发表时间:2010-09-06  
研究的好深啊。
0 请登录后投票
   发表时间:2010-09-06  
收藏一下回家看,这是在读源码丫
0 请登录后投票
   发表时间:2010-09-06  
楼主精神可嘉  俺当时培训的时候跟着源码写了一遍

文章较长  看起来还是觉得停轻松  毕竟亲自写过 

路漫漫其修远兮  吾将上下而求索
0 请登录后投票
   发表时间: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)))
不满足,则进入了死循环。



这个就是想问的答案
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics