今天被问到一个有意思的问题,你自己定义的hashmap长度真的有效吗?
要想了解这个问题,我们可以去源码中寻找答案(此博客中使用的是JDK1.8)
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
以上是HashMap源码的单独传长度参的构造函数,通过这里可以看出,此方法没有解决我们的疑问,是加上了默认负载因子后调用了双参数构造方法,有小伙伴会问,这个负载因子又是啥?不着急我们继续往下看
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
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);
}
这是之前方法调用的双参构造方法,乍一看下去就真让人头晕目眩,没事,路要一步一步走,我当时第一眼看下去就想直接关掉电脑,我逐行好好分析一下
第一二行代码都是判断长度是否合法,一个是最小值,一个是最大值,就像是汽水工厂里的质检员一样,你那瓶汽水太多了冒出来了,我就喝几口让他不至于冒出来。我们从第一行可以看出,最小值是0,如果少于0我就觉得你是来捣乱的,不做了,直接抛出异常,至于最大值,这里用MAXIMUM_CAPACITY来表示的,这个值到底是多大呢
1左移30位,这个具体值是
这也说明Hashmap最大长度就是这个值了,就算输入一个亿,那也是这个数
我们接着看代码,第三行是加载因子的判断,我们先引入加载因子的大概定义,是判断HashMap是否要扩容的阈值条件,它也和我们传入的initialCapacity一样,不能<=0,同时还引入了另一个方法,但是我们本次讨论的内容是传入的长度,所以我们暂且将它放在一边,镜头继续拉回到initialCapacity身上,第四和第五个代码是这个构造方法的最后一步了,但是我们的疑问还是没有解决,先看第四步,就是将负载因子直接赋值,但是第五步,这里调用了另一个方法,tableSizeFor,敲黑板,重点来了,我们自定义参数的成功与否就在这里了,来吧,真相的最后一步
我们先上源代码!
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
这是啥?一眼望去全是右移操作和按位或操作,但是最复杂的操作往往只需要最简单的运算!
按位或,有一则为一,举个例子,C=A|B,在二进制中,C中的1只会比A多或者一样,对于B也一样,说到这,有些同学可能已经领悟到了一点,这个操作就是将一个数转变为2的N次方数-1(二进制低位置全为1),可能很多同学还是对此保持疑惑,就这几步怎么可能,那么我们再来写一个实例129
第一步操作129-1=128
我们这里举的例子是一个很极端的例子,只有一个1,但是经过三次右移再进行按位或操作后,变成从最高位开始全1的数,这个数是255,再带入最后一步判断,大于0小于最大值,+1后返回,最终返回值是256
现在思考一下256与129和255之间有什么联系,我们再看一下二进制
这个操作的核心就是将一个数转换成大于最接近他或者等于它的2的n次方数
因此可得,我们传入的参数如果不是2的次方数,那么就会经过这个转换后成一个2的次方数,我们的疑问就可以解决了。
本人是一名大三学生,在学习之余写下,如果有不对之处欢迎大家指正哦。