HashMap指定数组大小为什么必须是2的n次幂
1.版本化的序列号
private static final long serialVersionUID = 362498820763181265L;
2.集合的初始化容器(必须是2的n次幂)
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
为什么必须是2的n次幂,如果不是2的2次幂会什么样子?
通过Hashmap添加一个元素的时候我们知道,hashmap需要根据key的hash去确定数组中的具体下标,HashMap为了提升高效,尽量较少碰撞,那么他就要尽量把数据均匀分配,让每个链表长度大致相同,这个实现就是把数据存到哪个链表中的算法。
这个算法其实就是取模,hash%length(数组长度),计算机直接求余数不如位运算效率高,所以源码中做了优化,使用hash&(length-1),hash%length等于hash&(length-1)的前提是length是2的n次幂。
2^4 is 10000
2^4-1 is 1111
If it is the n th power of 2
3 & (8-1)
00000011 3 //计算后的hash值
00000111 7 //数组长度-1
------------
00000011 3 索引为3
00000010 2
00000111 7
------------
00000010 2 索引为2
If not to the nth power of 2
3 & (9-1)
00000011 2
00001000 8
------------
00000000 0 索引为0
2 & (9-1)
00000010 2
00001000 8
------------
00000000 0 索引为0
计算出的索引特别容易相同,及其容器发生hash碰撞,导致其余数组空间很大程度上并没有存储数据,链表或者红黑树过长,效率降低。
总结:
由上面可以看出来,HashMap是根据key来确定数组的下标的,如果n为2幂次方就可以保证数据均匀的插入,如果不是2的幂次方,可能数组的一些位置永远也不会插入数据,这样就浪费了数组的空间还加大了hash冲突,通过%来确定位置效率远远不如&运算,但是如果使用&的话必须要保证length(数组长度)是2的n次幂。HashMap使用2的n次幂放就是为了保证数据均匀分布减少hash冲突,如果hash冲突越大,代表数组中的一个链或者红黑树长度越大,这样会降低hashmap的性能,
如果创建Hashmap对象的时候,指定的数组长度不是2幂次方,那么HashMap会通过位移运算得到比输入数字大的数组最近的2次幂
查看下我们给指定数组长度的HashMap源码
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
我们去看这个构造方法
public HashMap(int initialCapacity, float loadFactor) {
//如果我们给的长度小于0,就抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//如果我们给的长度大于2^30 那么就把2^30次方赋值给它,意思就是我们的长度必须小于2^30
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//设置数组长度
this.threshold = tableSizeFor(initialCapacity);
}
/**
* Returns a power of two size for the given target capacity.
* 或运算都是0的时候为0有1的时候为1
* 假设cap传入的是10
*/
static final int tableSizeFor(int cap) {
//这里-1是为了防止出现我们传入了16 还进行了右移运算,假如我们输入成16 通过-1然后最后右移算出来是16
int n = cap - 1;
/*
00001001 9
00000100 4 9 >>> 1
--------------
00001101 13 9 |= 4
*/
n |= n >>> 1;
/*
00001101 13
00000011 3
---------------
00001111 15
*/
n |= n >>> 2;
/*
00001111 15
00000000 0
--------------
00001111 15
*/
n |= n >>> 4;
/*
00001111 15
00000000 0
--------------
00001111 15
*/
n |= n >>> 8;
/*
00001111 15
00000000 0
--------------
00001111 15
*/
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
总结
如果给的值不是2的幂次方,那么就通过底层右移操作,变成大于你指定的数字最小的二次幂
我们传入的数组长度大小不能超过2^30