作者 | 小牛

Java 工程师,关注服务端技术
引子
最近在看 JDK 的 ThreadLocal
源码时,发现了一段有意思的代码,如下所示。
private final int threadLocalHashCode = nextHashCode();
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/ private static final int HASH_INCREMENT = 0x61c88647 ; /**
* Returns the next hash code.
*/ private static int nextHashCode () { return nextHashCode . getAndAdd ( HASH_INCREMENT ); }
可以看到,其中定义了一个魔法值 HASH_INCREMENT = 0x61c88647
,对于实例变量 threadLocalHashCode
,每当创建 ThreadLocal
实例时这个值都会getAndAdd(0x61c88647)
。
0x61c88647
转化成二进制即为 1640531527
,它常用于在散列中增加哈希值。上面的代码注释中也解释到:HASH_INCREMENT
是为了让哈希码能均匀的分布在2的N次方的数组里。
那么 0x61c88647
是怎么起作用的呢?
什么是散列?
ThreadLocal 使用一个自定的的 Map —— ThreadLocalMap 来维护线程本地的值。首先我们先了解一下散列的概念。
散列(Hash)也称为哈希,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,这个输出值就是散列值。
在实际使用中,不同的输入可能会散列成相同的输出,这时也就产生了冲突。通过上文提到的 HASH_INCREMENT
再借助一定的算法,就可以将哈希码能均匀的分布在 2 的 N 次方的数组里,保证了散列表的离散度,从而降低了冲突几率.
哈希表就是将数据根据散列函数 f(K)
映射到表中的特定位置进行存储。因此哈希表最大的特点就是可以根据 f(K)
函数得到其索引。
HashMap 就是使用哈希表来存储的,并且采用了链地址法解决冲突。
简单来说,哈希表的实现就是数组加链表的结合。在每个数组元素上都一个链表结构,当数据被 Hash 后,得到数组下标,把数据放在对应下标元素的链表上。
散列算法
先来说一下散列算法。散列算法的宗旨就是:构造冲突较低的散列地址,保证散列表中数据的离散度。常用的有以下几种散列算法:
除法散列法
散列长度 m, 对于一个小于 m 的数 p 取模,所得结果为散列地址。对 p 的选择很重要,一般取素数或 m
公式:f(k) = k % p (p<=m)