IdentityHashMap.hash() 中这段代码的用途是什么?
What is the purpose of this code in IdentityHashMap.hash()?
/**
* Returns index for Object x.
*/
private static int hash(Object x, int length) {
int h = System.identityHashCode(x);
// Multiply by -127, and left-shift to use least bit as part of hash
return ((h << 1) - (h << 8)) & (length - 1);
}
发件人:jdk/IdentityHashMap.java at jdk8-b120 · openjdk/jdk · GitHub
理论上System.identityHashCode()
返回的hash值已经是均匀分布的了,那为什么还要多一个移位运算而不是直接和length - 1
进行AND运算呢?
该实现似乎保证最低位为 0 以确保计算结果为偶数,因为该实现要求所有键都在偶数索引上,所有值都在奇数索引上。
h << 8
似乎混合了低位和高位来处理当System.identityHashCode()实现为内存地址或递增值时的情况,目前尚不清楚为什么只有8位在这里移动而不是类似 HashMap.hash()
的东西也移动 16 位。
代码中的注释说:
"Implementation note: This is a simple linear-probe hash table, as described for example in texts by Sedgewick and Knuth. The array alternates holding keys and values."
实际上 hash
方法 return 是一个用作数组直接索引的值。例如:
public V get(Object key) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len);
while (true) {
Object item = tab[i];
if (item == k)
return (V) tab[i + 1];
if (item == null)
return null;
i = nextKeyIndex(i, len);
}
}
这意味着 hash
需要 return 一个偶数。 hash
中的计算确保索引是均匀的,而不会丢弃 System.identityHashCode(x)
值的底部位。
为什么不直接扔掉底部位呢?
好吧,答案在于 System.identityHashCode
的实现方式。实际上,有多种生成哈希的算法,所使用的算法(在运行时)取决于一个晦涩的 JVM 命令行选项。
一些算法(名义上)均匀分布在 int
的范围内。对于那些,丢弃底部位就可以了。
其他算法不是这样的。其中一种算法使用简单的全局计数器。另一个使用对象的内存地址,删除了低 3 位。如果选择这些算法,丢弃 LSB 会增加 IdentityHashMap
.
中哈希冲突的概率
有关 IdentityHashcode
算法及其选择方式的详细信息,请参阅 https://shipilev.net/jvm/anatomy-quarks/26-identity-hash-code/。请注意,JVM 行为的这一方面是未指定的,并且可能是特定于版本的。
我的预感是它旨在解决两个问题。
首先,该函数产生的槽索引必须是偶数。 (该实现将键存储在偶数 table 槽中,将值存储在奇数 table 槽中。)这意味着无论返回什么索引,其最后一位都必须等于零。
其次,使用的身份哈希码(可能)基于内存地址,内存地址的低位比高位“更随机”。例如,如果我们分配一个对象列表,并且分配器将它们全部连续地放入内存中,那么它们的地址都将具有相同的高位但不同的低位。 (或者也许只有一个对象的全局计数器在创建对象时递增。在这种情况下,对象哈希的低位将同样比高位具有更广泛的分散。)
为了确保事情在 table 中分散开来,因此我们希望将哈希码的低位与哈希码的高位“混合”。减去 h << 8
的效果是将身份哈希码的低位向上移动,翻转它们,然后将它们添加回哈希码,从而在加法运算时产生一堆“涟漪”。我认为(?)这是一种有效的方法,可以将更高熵的低位注入高位,一旦 table 开始变得越来越大,就可以在插槽数组上提供更均匀的散列。
/**
* Returns index for Object x.
*/
private static int hash(Object x, int length) {
int h = System.identityHashCode(x);
// Multiply by -127, and left-shift to use least bit as part of hash
return ((h << 1) - (h << 8)) & (length - 1);
}
发件人:jdk/IdentityHashMap.java at jdk8-b120 · openjdk/jdk · GitHub
理论上System.identityHashCode()
返回的hash值已经是均匀分布的了,那为什么还要多一个移位运算而不是直接和length - 1
进行AND运算呢?
该实现似乎保证最低位为 0 以确保计算结果为偶数,因为该实现要求所有键都在偶数索引上,所有值都在奇数索引上。
h << 8
似乎混合了低位和高位来处理当System.identityHashCode()实现为内存地址或递增值时的情况,目前尚不清楚为什么只有8位在这里移动而不是类似 HashMap.hash()
的东西也移动 16 位。
代码中的注释说:
"Implementation note: This is a simple linear-probe hash table, as described for example in texts by Sedgewick and Knuth. The array alternates holding keys and values."
实际上 hash
方法 return 是一个用作数组直接索引的值。例如:
public V get(Object key) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len);
while (true) {
Object item = tab[i];
if (item == k)
return (V) tab[i + 1];
if (item == null)
return null;
i = nextKeyIndex(i, len);
}
}
这意味着 hash
需要 return 一个偶数。 hash
中的计算确保索引是均匀的,而不会丢弃 System.identityHashCode(x)
值的底部位。
为什么不直接扔掉底部位呢?
好吧,答案在于 System.identityHashCode
的实现方式。实际上,有多种生成哈希的算法,所使用的算法(在运行时)取决于一个晦涩的 JVM 命令行选项。
一些算法(名义上)均匀分布在
int
的范围内。对于那些,丢弃底部位就可以了。其他算法不是这样的。其中一种算法使用简单的全局计数器。另一个使用对象的内存地址,删除了低 3 位。如果选择这些算法,丢弃 LSB 会增加
中哈希冲突的概率IdentityHashMap
.
有关 IdentityHashcode
算法及其选择方式的详细信息,请参阅 https://shipilev.net/jvm/anatomy-quarks/26-identity-hash-code/。请注意,JVM 行为的这一方面是未指定的,并且可能是特定于版本的。
我的预感是它旨在解决两个问题。
首先,该函数产生的槽索引必须是偶数。 (该实现将键存储在偶数 table 槽中,将值存储在奇数 table 槽中。)这意味着无论返回什么索引,其最后一位都必须等于零。
其次,使用的身份哈希码(可能)基于内存地址,内存地址的低位比高位“更随机”。例如,如果我们分配一个对象列表,并且分配器将它们全部连续地放入内存中,那么它们的地址都将具有相同的高位但不同的低位。 (或者也许只有一个对象的全局计数器在创建对象时递增。在这种情况下,对象哈希的低位将同样比高位具有更广泛的分散。)
为了确保事情在 table 中分散开来,因此我们希望将哈希码的低位与哈希码的高位“混合”。减去 h << 8
的效果是将身份哈希码的低位向上移动,翻转它们,然后将它们添加回哈希码,从而在加法运算时产生一堆“涟漪”。我认为(?)这是一种有效的方法,可以将更高熵的低位注入高位,一旦 table 开始变得越来越大,就可以在插槽数组上提供更均匀的散列。