知道为啥HashMap里面的数组size必须是2的次幂?

最近在写一个简易的分离锁的类:

 

要求:对不同的Key进行hash得到一个Lock,并要求对锁映射的概率差不多。比如,160个Key,分布到16个锁上,大概有10个Key是映射到同一个锁上的,只要这样并发效率才会高。

 

Java代码   收藏代码
  1. public class SplitReentrantLock {  
  2.   
  3.     private Lock[] locks;  
  4.   
  5.     private int LOCK_NUM;  
  6.   
  7.     public SplitReentrantLock(int lockNum) {  
  8.         super();  
  9.         LOCK_NUM = lockNum;  
  10.         locks = new Lock[LOCK_NUM];  
  11.         for (int i = 0; i < LOCK_NUM; i++) {  
  12.             locks[i] = new ReentrantLock();  
  13.         }  
  14.     }  
  15.   
  16.     /** 
  17.      * 获取锁, 使用HashMap的hash算法 
  18.      *  
  19.      *  
  20.      * @param key 
  21.      * @return 
  22.      */  
  23.     public Lock getLock(String key) {  
  24.   
  25.         int lockIndex = index(key);  
  26.         return locks[lockIndex];  
  27.     }  
  28.   
  29.     int index(String key) {  
  30.         int hash = hash(key.hashCode());          
  31.         return hash & (LOCK_NUM - 1);  
  32.     }  
  33.   
  34.     int hash(int h) {  
  35.         h ^= (h >>> 20) ^ (h >>> 12);  
  36.         return h ^ (h >>> 7) ^ (h >>> 4);  
  37.     }  

 

 

用法:

 

Java代码   收藏代码
  1. SplitReentrantLock locks = new SplitReentrantLock(16);  
  2.   Lock lock =locks.getLock(key);   
  3.   lock.lock();  
  4.   try{  
  5.      //......  
  6.    }finally{  
  7.    lock.unlock();   
  8.    }  

 

本来认为用HashMap的hash算法就能够将 达到上述的要求,结果测试的时候吓了一跳。

 

测试代码:

 

 

Java代码   收藏代码
  1. public class SplitReenterLockTest extends TestCase {  
  2.   
  3.     public void method(int lockNum, int testNum) {  
  4.   
  5.         SplitReentrantLock splitLock = new SplitReentrantLock(lockNum);  
  6.         Map<Integer, Integer> map = new TreeMap<Integer, Integer>();  
  7.         for (int i = 0; i < lockNum; i++) {  
  8.             map.put(i, 0);  
  9.         }  
  10.         for (int i = 0; i < testNum; i++) {  
  11.             Integer key = splitLock.index(RandomStringUtils.random(128));  
  12.             map.put(key, map.get(key) + 1);  
  13.         }  
  14.   
  15.         for (Map.Entry<Integer, Integer> entry : map.entrySet()) {  
  16.             System.out.println(entry.getKey() + " : " + entry.getValue());  
  17.         }  
  18.     }  
  19.   
  20.     public void test1() {  
  21.         method(501000);}  
  22.    
  23. }  

 

 

结果:1000个随机key的hash只是映射到8个 Lock上,而不是平均到50个Lock上。

而且是固定分布到0,1,16,17,32,33,48,49的数组下标对应的Lock上面,这是为什么呢?

 

如果改为:

 

Java代码   收藏代码
  1. public void test1() {  
  2.     method(321000);  
  3. }  

 

 结果:1000个随机key的hash 映射到32个Lock上,而且基本上是平均分布的。

 

问题 :为什么50和32的hash的效果差别那么大呢?

 

再次测试2,4,8,16,64,128. 发现基本上都是平均分布到所有的Lock上面。

 

得到平均分布的这些数都是2的次幂,难道hash算法和二进制有关?

 

看看hash算法:   

 

Java代码   收藏代码
  1.   int index(String key) {  
  2.     int hash = hash(key.hashCode());          
  3.     return hash & (LOCK_NUM - 1);  
  4. }  
  5.   
  6. int hash(int h) {  
  7.     h ^= (h >>> 20) ^ (h >>> 12);  
  8.     return h ^ (h >>> 7) ^ (h >>> 4);  
  9. }  

 先是经过神奇的(ps:不知道为什么这么运算,无知的我只能用神奇来形容)的位运算,最后和LOCK_NUM - 1来进行与运算。

 

本帖的关键点就是在于这个与运算中,如果要想运算后的结果是否平均分布,在于LOCK_NUM-1的二进制中1的位数有几个。如果都是1,那么肯定是平均分布到0至LOCK_NUM-1上面。否则仅仅分布指定的几位。

 

下面以50和32说明:

 

假设Key进行hash运行得到hash值为h,

比如:我测试的数据中的一些h的二进制值:

 

Java代码   收藏代码
  1. 1100000010000110110101010001001  
  2. 10111100001001110111000100010001  
  3. 11111011111010101010000111001001  
  4. 11001010011000100110110111011111  
  5. 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的平均分布 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值