HashMap
的规约
JavaDocs中HashMap
的spec是这么写的:
Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The
HashMap
class is roughly equivalent toHashtable
, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.
这里头可提取出几个关键的信息:
- 基于
Map
接口实现 - 允许
null
键/值 - 非同步
- 不保证有序(如插入的顺序)
- 不保证顺序不随时间变化。
这些问题在上篇都已经基本解释清楚了。下篇中,我们顺着HashMap
的规约继续往下探索。
两个因子
有两个因子直接影响HashMap
的效率,
- 初始容量(initial capacity)。容量是指哈希表中桶的数量,而初始容量顾名思义就是
HashMap
实例创建时的最初容量。 - 载入因子(Load factor)。含义在上篇已经阐述。
JavaDocs的原文对这两个因子是这样描述的:
- Initial capacity. The capacity is the number of buckets in the hash table, The initial capacity is simply the capacity at the time the hash table is created.
- Load factor. The load factor is a measure of how full the hash table is allowed to get before its capacity is automatically increased.
简单的说,容量(Capacity)就是桶(buckets)的数目,载入因子就是有数据的桶占所有桶的最大比例。如果对迭代性能要求很高的话不要把容量设置过大,也不要把载入因子设置过小。当有数据的桶的数目(即HashMap
中元素的个数)大于Capacity * LoadFactor
时就需要调整桶的数目为当前的两倍,也就是上篇所说的扩张。
初始容量
HashMap
的默认初始容量为16。把它设成2的幂次是有意为之。我们再回顾一下index的计算:
index = hash(Key) & (length - 1)
因为length
是,所以length - 1
是,也就是个1,index就相当于是hash(Key)
的最后位。这样,只要保证hash(Key)
是均匀的,index的分布也就一定是均匀的。此外,这样设计算法使得可以用位运算计算index,避免了取模等效率低下的运算方式,提高了运算效率。
另外,需要注意的是,hash(Key)
并不是hashCode(Key)
。hash
方法的定义如下:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
可以看到这个函数大概的作用就是:高16位不变,低16位和高16位做了一个异或。其中代码注释是这样写的:
Computes
key.hashCode()
and spreads (XORs) higher bits of hash to lower. Because the table uses power-of-two masking, sets of hashes that vary only in bits above the current mask will always collide. (Among known examples are sets of Float keys holding consecutive whole numbers in small tables.) So we apply a transform that spreads the impact of higher bits downward. There is a tradeoff between speed, utility, and quality of bit-spreading. Because many common sets of hashes are already reasonably distributed (so don’t benefit from spreading), and because we use trees to handle large sets of collisions in bins, we just XOR some shifted bits in the cheapest possible way to reduce systematic lossage, as well as to incorporate impact of the highest bits that would otherwise never be used in index calculations because of table bounds.
也就是说,设计者认为直接用hashCode(Key)
的值很容易发生碰撞。为什么这么说呢?不妨思考一下,在为15(0x1111
)时,其实散列真正生效的只是低4位,当然容易碰撞了。
因此,设计者采取了顾全大局的方法(综合考虑了速度、作用、质量),即把高16位和低16位异或了一下(也就是hash
方法的作用)。设计者还解释到因为现在大多数的hashCode
的分布已经很不错了,就算是发生了碰撞也用的红黑树去做了(下文会提到)。仅仅异或一下,既减少了系统的开销,也不会造成的因为高位没有参与下标的计算而引起的碰撞。
至于为什么偏偏设成2的4次幂,一般认为没什么特别的理由,用4次幂可能只是因为作者认为16这个初始容量是能符合常用的情景而已。