NanguoCoffee 知道为啥HashMap里面的数组size必须是2的次幂?
2014-07-28 20:27
239 查看
最近在写一个简易的分离锁的类:
要求:对不同的Key进行hash得到一个Lock,并要求对锁映射的概率差不多。比如,160个Key,分布到16个锁上,大概有10个Key是映射到同一个锁上的,只要这样并发效率才会高。
用法:
测试代码:
结果:1000个随机key的hash只是映射到8个Lock上,而不是平均到50个Lock上。
而且是固定分布到0,1,16,17,32,33,48,49的数组下标对应的Lock上面,这是为什么呢?
如果改为:
结果:1000个随机key的hash 映射到32个Lock上,而且基本上是平均分布的。
问题:为什么50和32的hash的效果差别那么大呢?
再次测试2,4,8,1
b882
6,64,128. 发现基本上都是平均分布到所有的Lock上面。
得到平均分布的这些数都是2的次幂,难道hash算法和二进制有关?
看看hash算法:
先是经过神奇的(ps:不知道为什么这么运算,无知的我只能用神奇来形容)的位运算,最后和LOCK_NUM - 1来进行与运算。
本帖的关键点就是在于这个与运算中,如果要想运算后的结果是否平均分布,在于LOCK_NUM-1的二进制中1的位数有几个。如果都是1,那么肯定是平均分布到0至LOCK_NUM-1上面。否则仅仅分布指定的几位。
下面以50和32说明:
假设Key进行hash运行得到hash值为h,
比如:我测试的数据中的一些h的二进制值:
50的二进制值:110010.减去1后的二进制:110001
32的二进制值: 100000.减去1后的二进制:11111
因此h和 49 (即110001)与的结果只能为
000000 : 0
000001 : 1
010000 : 16
010001 : 17
100000 : 32
100001 : 33
110000 : 48
110001 : 49
而h和31 (即11111)与的结果为:
00000
00001
00010
....
11110
11111
这下知道原因了吧。LOCK_NUM -1 二进制中为1的位数越多,那么分布就平均。
这也就是为什么HashMap默认大小为2的次幂,并且添加元素时,如果超过了一定的数量,那么就将数量增大到原来的两倍,其中非常重要的原因就是为了hash的平均分布。
要求:对不同的Key进行hash得到一个Lock,并要求对锁映射的概率差不多。比如,160个Key,分布到16个锁上,大概有10个Key是映射到同一个锁上的,只要这样并发效率才会高。
public void test1() { method(32, 1000); }
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">本来认为用HashMap的hash算法就能够将 达到上述的要求,结果测试的时候吓了一跳。</span>
测试代码:
package hash; import java.util.Map; import java.util.TreeMap; import org.apache.commons.lang3.RandomStringUtils; import junit.framework.TestCase; public class SplitReenterLockTest extends TestCase { <span style="white-space:pre"> </span> public void method(int lockNum, int testNum) { SplitReentrantLock splitLock = new SplitReentrantLock(lockNum); Map<Integer, Integer> map = new TreeMap<Integer, Integer>(); for (int i = 0; i < lockNum; i++) { map.put(i, 0); } for (int i = 0; i < testNum; i++) { Integer key = splitLock.index(RandomStringUtils.random(128)); map.put(key, map.get(key) + 1); } for (Map.Entry<Integer, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + " : " + entry.getValue()); } } public void test1() { method(50, 1000); } }
结果:1000个随机key的hash只是映射到8个Lock上,而不是平均到50个Lock上。
而且是固定分布到0,1,16,17,32,33,48,49的数组下标对应的Lock上面,这是为什么呢?
如果改为:
<pre code_snippet_id="437309" snippet_file_name="blog_20140728_2_7660145" name="code" class="java">public void test1() { method(32, 1000); }
结果:1000个随机key的hash 映射到32个Lock上,而且基本上是平均分布的。
问题:为什么50和32的hash的效果差别那么大呢?
再次测试2,4,8,1
b882
6,64,128. 发现基本上都是平均分布到所有的Lock上面。
得到平均分布的这些数都是2的次幂,难道hash算法和二进制有关?
看看hash算法:
public int index(String key) { <span style="white-space:pre"> </span>int hash = hash(key.hashCode()); <span style="white-space:pre"> </span>return hash & (LOCK_NUM - 1); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>private int hash(int h) { <span style="white-space:pre"> </span>h ^= (h >>> 20)^(h >>> 12); <span style="white-space:pre"> </span>return h^(h>>>7)^(h>>>4); <span style="white-space:pre"> </span>}
先是经过神奇的(ps:不知道为什么这么运算,无知的我只能用神奇来形容)的位运算,最后和LOCK_NUM - 1来进行与运算。
本帖的关键点就是在于这个与运算中,如果要想运算后的结果是否平均分布,在于LOCK_NUM-1的二进制中1的位数有几个。如果都是1,那么肯定是平均分布到0至LOCK_NUM-1上面。否则仅仅分布指定的几位。
下面以50和32说明:
假设Key进行hash运行得到hash值为h,
比如:我测试的数据中的一些h的二进制值:
1100000010000110110101010001001 10111100001001110111000100010001 11111011111010101010000111001001 11001010011000100110110111011111 10001010100010111101011010011110
50的二进制值:110010.减去1后的二进制:110001
32的二进制值: 100000.减去1后的二进制:11111
因此h和 49 (即110001)与的结果只能为
000000 : 0
000001 : 1
010000 : 16
010001 : 17
100000 : 32
100001 : 33
110000 : 48
110001 : 49
而h和31 (即11111)与的结果为:
00000
00001
00010
....
11110
11111
这下知道原因了吧。LOCK_NUM -1 二进制中为1的位数越多,那么分布就平均。
这也就是为什么HashMap默认大小为2的次幂,并且添加元素时,如果超过了一定的数量,那么就将数量增大到原来的两倍,其中非常重要的原因就是为了hash的平均分布。
相关文章推荐
- 知道为啥HashMap里面的数组size必须是2的次幂?
- 知道为啥HashMap里面的数组size必须是2的次幂?
- 知道为啥HashMap里面的数组size必须是2的次幂?
- 什么是 哈希表 HashMap 中数组的 size 为什么必须是 2 的整数次幂
- Java编程:浅析 HashMap 中数组的 size 为什么必须是 2 的整数次幂
- 你必须知道的指针基础-4.sizeof计算数组长度与strcpy的安全性问题
- 阿里巴巴面试算法题:有一个函数int getNum(),每运行一次可以从一个数组V[N]里面取出一个数,N未知,当数取完的时候,函数返回NULL。现在要求写一个函数int get(),这个函数运行一次可以从V[N]里随机取出一个数,而这个数必须是符合1/N
- 准程序员必须知道的内存那点事儿----指针域数组的对比
- 数组必须知道的知识
- hashMap为啥初始化容量为2的次幂
- 再解为啥c里函数传入数组时必须同…
- 你必须知道的指针基础-2.指针的声明和使用及数组和指针的关系
- hashMap为啥初始化容量为2的次幂
- ARM你必须知道的事儿——为啥“PC = PC + 8”?
- hashMap为啥初始化容量为2的次幂
- 程序员必须知道的10大基础实用算法及其讲解
- 安卓开发,你必须知道的一些基本第三方
- 必须知道的八大种排序算法【java实现】(三) 归并排序算法、堆排序算法详解
- Android Context 上下文 你必须知道的一切
- facebook 15个你必须知道的开源项目