HashMap里有许多常量,开始的时候当做一个面试点去记忆,但是可能或多或少都在面试中被问到过为什么是这个值而不是其他的值,本文就来简单分析一下这些常量,本章内容包括
一、默认初始长度为什么是16?
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
注意这里并没有直接写16,而是用的位运算,所以我们需要分析两点:
1、为什么是2的幂次方?
2、为什么是2的四次方而不是三次方或者五次方等其他?
先回答第一个问题,为什么是2的幂次方?
作为一种集合,HashMap需要存储数据,既然是存储数据,自然就要定义数据的位置,而HashMap采用的hash寻址法,即用hash值对哈希桶的长度进行取模,得到的余数就是数据在哈希桶的位置
而如果哈希桶的长度是2的幂次方,那么用hash值和长度减一进行的位运算等同于直接用hash对长度取模,即hash & (n-1) 等同于 hash % n,至于为什么进行位运算而不是取模,则是因为位运算的效率比取模要高的多
接着回答第二个问题,为什么是2的四次方?
如果长度过小,则容易出现哈希碰撞,并且容易扩容,影响效率
如果长度过大,则容易浪费内存
这里可以用ArrayList做一下简单的类比,ArrayList的初始容量是10,它需要等空间用完后才进行扩容,也就是说开始扩容的阈值等于10,8虽然比16更接近10,但是如果考虑扩容阈值的话,16的阈值(12)比8的阈值(6)更接近10
二、为什么容量的最大值是2的30次方,而不是2的31次方?
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
我们知道int类型的最大值等于2的31次方减一,但是二进制的左位是符号位,容量必不能为负数,所以只有30位可以用,因此最大值为2的30次方
三、负载因子为什么是0.75?
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
这里需要先说明一下负载因子的作用,主要是用来控制扩容的阈值,来决定是否扩容
如果负载因子过小,则阈值小,容易扩容,造成空间的浪费
反正如果过大,则阈值大,容易出现哈希碰撞
因此需要取一个两者之间一个平衡的值,而0.75就是根据经验和实践这样的来的
不过也可以从下面这个角度尝试分析,假设对于哈希桶某个位置的节点是否为空的概率为0.5,那么根据牛顿二项式计算出来的负载因子为log(2),约等于0.693(差不多7/10),考虑到计算取整的话,0.75(3/4)比较合适
四、链表转红黑树的数量为什么是8?
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
这里可以看一下JDK对此的说明
* Because TreeNodes are about twice the size of regular nodes, we
* use them only when bins contain enough nodes to warrant use
* (see TREEIFY_THRESHOLD). And when they become too small (due to
* removal or resizing) they are converted back to plain bins. In
* usages with well-distributed user hashCodes, tree bins are
* rarely used. Ideally, under random hashCodes, the frequency of
* nodes in bins follows a Poisson distribution
* (http://en.wikipedia.org/wiki/Poisson_distribution) with a
* parameter of about 0.5 on average for the default resizing
* threshold of 0.75, although with a large variance because of
* resizing granularity. Ignoring variance, the expected
* occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
* factorial(k)). The first values are:
*
* 0: 0.60653066
* 1: 0.30326533
* 2: 0.07581633
* 3: 0.01263606
* 4: 0.00157952
* 5: 0.00015795
* 6: 0.00001316
* 7: 0.00000094
* 8: 0.00000006
* more: less than 1 in ten million
总结来说就是理想状态中,在随机哈希码情况下,对于默认0.75的加载因子,桶中节点的分布频率服从参数为0.5的泊松分布,即使粒度调整会产生较大方差。
由对照表,可以看到链表中元素个数为8时的概率非常非常小了,所以链表转换红黑树的阀值选择8
五、为什么红黑树转链表的数量是6?
/**
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
static final int UNTREEIFY_THRESHOLD = 6;
这是为了防止链表和树之间频繁的转换。如果是7的话,假设一个HashMap不停的插入、删除元素,链表个数一直在8左右徘徊,就会频繁树转链表、链表转树,效率非常低下。
六、哈希表最小树形化的容量为什么是64?
/**
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
static final int MIN_TREEIFY_CAPACITY = 64;
这是因为容量低于64时,哈希碰撞的机率比较大,而这个时候出现长链表的可能性会稍微大一些,这种原因下产生的长链表,我们应该优先选择扩容而避免不必要的树化。