构造函数:
默认构造函数什么都不做,只是将加载因子设为默认加载因子。
有初始值大小的构造函数,
会将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。