构造函数:
默认构造函数什么都不做,只是将加载因子设为默认加载因子。
有初始值大小的构造函数,
会将threshold设置为大于输入参数且最近的2的整数次幂的数,比如10,设置阈值16.
即使有initialCapacity参数的构造,也是设置threshold,不会现在设置cap,如要设置cap就需要new资源了,而原理是在等真的插入的时候才去通过resize操作申请内存资源, 见resize.md,put.md
重点:
1.参数最大容量,默认的加载因子,加载因子,阈值
2.哈希桶, Node
3.loadFactor和threshold的关系
4.tableSizeFor函数的原理, 见下面解析
默认参数和构造函数源码:
//最大容量 2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//哈希桶,存放链表。 长度是2的N次方,或者初始化时为0.
transient Node<K,V>[] table;
//加载因子,用于计算哈希表元素数量的阈值。 threshold = 哈希桶.length * loadFactor;
final float loadFactor;
//哈希表内元素数量的阈值,当哈希表内元素数量超过阈值时,会发生扩容resize()。
int threshold;
public HashMap() {
//默认构造函数,赋值加载因子为默认的0.75f
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(int initialCapacity) {
//指定初始化容量的构造函数
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//同时指定初始化容量 以及 加载因子, 用的很少,一般不会修改loadFactor
public HashMap(int initialCapacity, float loadFactor) {
//边界处理
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//初始容量最大不能超过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;
//设置阈值为 >= 初始化容量的最近的2的n次方的值
this.threshold = tableSizeFor(initialCapacity);
}
//新建一个哈希表,同时将另一个map m 里的所有元素加入表中
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
tableSizeFor的功能(不考虑大于最大容量的情况)是返回大于输入参数且最近的2的整数次幂的数。比如10,则返回16.
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;
}
先来分析有关n位操作部分:先来假设n的二进制为01xxx…xxx。接着
对n右移1位:001xx…xxx,再位或:011xx…xxx
对n右移2为:00011…xxx,再位或:01111…xxx
此时前面已经有四个1了,再右移4位且位或可得8个1
同理,有8个1,右移8位肯定会让后八位也为1。
综上可得,该算法让最高位的1后面的位全变为1。
最后再让结果n+1,即得到了2的整数次幂的值了。
现在回来看看第一条语句:
int n = cap - 1;
让cap-1再赋值给n的目的是另找到的目标值大于或等于原值。例如二进制1000,十进制数值为8。如果不对它减1而直接操作,将得到答案10000,即16。显然不是结果。减1后二进制为111,再进行操作则会得到原来的数值1000,即8。
HashMap中size表示当前共有多少个KV对,capacity表示当前HashMap的容量是多少,默认值是16,每次扩容都是成倍的。loadFactor是装载因子,当Map中元素个数超过loadFactor capacity的值时,会触发扩容。loadFactor capacity可以用threshold表示。
好多地方都说threshold = loadFactor capacity, 但有初始值的初始化的时候,threadshold由tableSizeFor函数获得,这个地方之后 threshold 是什么时间进行的调整,应该是之后调整是根据loadFactor capacity。