论坛首页 Java企业应用论坛

HashMap深度分析

浏览 52061 次
该帖已经被评为精华帖
作者 正文
   发表时间:2011-07-19  
引用

为啥默认的最大容量为2**30,而不是2**31-1呢?

最大容量%2应该等于0.
0 请登录后投票
   发表时间:2011-07-25  
分析的很棒..以前我也看过这个源代码..但是没用去细细推敲..collection framework写的很清晰的, joush block大师的杰作...看过大这个框架的大部分代码, 但是, 没有去细细推敲...所以, 现在没什么印象了...看完楼主的分析, 感觉真好..
0 请登录后投票
   发表时间:2011-07-26   最后修改:2011-07-26
uule 写道
zhang34082 写道
在方法中:
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;
                    e = next;
                } while (e != null);
            }
        }
    }


由于 使用了新的newCapacity,indexFor出来的值 就不会一样,这一句
e.next = newTable[i]; 为什么不写成 e.next = null;

希望牛人回答

---
呵呵  想明白了 


我也有这个疑问。。。什么原因?


假设数组初始容量为 4
元素A的hash为 1101 ,在 table中的index为 1101 & 11=01
元素B的hash为 1001 ,在 table中的index为 1001 & 11=01
所以他们是挂在同个index下的链表上的

当数组扩容到了8后
元素A的在新 table中的index为 1101 & 111=101
元素B的在新 table中的index为 1001 & 111=001
他们在新table中是不能挂在同个index下的链表上了

假设还有个C的hash与B相同,那么在旧table中他们3是挂在同个list上的
而在新的table上只有B、C是在同个list上了

这就是为什么扩容后需要再hash,因为在新table中分布已经变了
0 请登录后投票
   发表时间:2011-07-26   最后修改:2011-07-26
引用

所以length-1一定是个奇数,假设现在长度为16,减去1后就是15,对应的二进制是:1111。

假设有两个元素,一个哈希值是8,二进制是1000,一个哈希值是9,二进制是1001。和1111与运算后,分别还是1000和1001,它们被分配在了数组的不同位置,这样,哈希的分布非常均匀。

那么,如果数组长度是奇数,减去1后就是偶数了,偶数对应的二进制最低位一定是0了,例如14二进制1110。对上面两个数子分别与运算,得到1000和1000。看到了吗?都是一样的值,哈希值8和9的元素多被存储在数组同一个位置的链表中。在操作的时候,链表中的元素越多,效率越低,因为要不停的对链表循环比较。所以,一定要哈希均匀分布,尽量减少哈希冲突,减少了哈希冲突,就减少了链表循环,就提高了效率。


这么解释有点因果不清了
为什么要这么做其实得从hash的内部存储分析
因为table是个数组,indexFor根据hash计算在table中存储的index值要落在区间 [0,length-1] 内
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

与 length-1 进行 & 的结果就是index(也就是其值刚好落在上述区间内),这才是 【因】。
同时,元素的分布还是应该由hash值决定,indexFor不应该改变hash的分布
(实际上indexFor的操作结果是个均匀分布,这就好比数学里的 n*1=n )。

为了实现一个存储均匀分布的indexFor函数,所以要求length-1的值是 11111...(二进制位所有位上的值为1)的。
于是才有了要求length为2的次方的 【果】。


PS: HashMap中的那个 hash函数
    static int hash(int h) {
	return useNewHash ? newHash(h) : oldHash(h);
    }

oldHash的具体原理没深入,但我分析这是为了处理:
当Map较小时,存入数据的hash在高位(大数范畴内)分布均匀,而在低位(小数范畴内)分布不均匀的情况。
0 请登录后投票
   发表时间:2011-07-26  
       
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);

        这个hash的算法一直没明白,有大牛能给讲讲为什么要这样吗?
0 请登录后投票
   发表时间:2011-07-27   最后修改:2011-07-27
不错,分享了!

另外,从楼主的观点来看,代码:

/**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }


length -1 的原因是由于奇偶效应,而楼上却有人说是区间效应。我觉得都有道理,不知道楼主目前的理解是怎样的
0 请登录后投票
   发表时间:2011-07-27  
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]


这个例子有点问题吧,2与3和7的i值都是2,怎么会是6呢?
0 请登录后投票
   发表时间:2011-07-30  
佩服楼主,分析的这么透彻,并发下有时还是使用HashMap的需要,
楼主可以试试 Collections.synchronizedMap(new HashMap()); 这样获取的是个线程安全的HashMap
0 请登录后投票
论坛首页 Java企业应用版

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