从CLRS(“算法导论”)一书中,有几种散列函数,如mod,multiply等。
Java使用什么散列函数将密钥映射到插槽?
我看到这里有一个问题Hashing function used in Java Language。但它没有回答这个问题,我认为这个问题的明确答案是错误的。它说hashCode()允许你为Hashtable做你自己的散列函数,但我认为这是错误的。
hashCode()返回的整数是Hashtble的真正密钥,然后Hashtable使用散列函数来散列hashCode()。这个答案暗示的是Java让你有机会给Hashtable一个散列函数,但不,这是错误的。 hashCode()给出了真正的密钥,而不是散列函数。
那么Java使用的哈希函数究竟是什么?
答案 0 :(得分:94)
当在OpenJDK中向HashMap添加或请求密钥时,执行流程如下:
hashCode()
方法将密钥转换为32位值。如果选择适当高的哈希表大小,则冲突的数量将受到限制。因此,单个查找平均只需要恒定的时间。这称为预期的恒定时间。但是,如果攻击者控制插入哈希表的密钥并了解正在使用的哈希算法,他可能会引发大量哈希冲突,从而迫使线性查找时间。这就是为什么最近一些哈希表实现已被更改为包含一个随机元素,使攻击者更难以预测哪些键会导致冲突。
key.hashCode()
|
| 32-bit value
| hash table
V +------------+ +----------------------+
HashMap.hash() --+ | reference | -> | key1 | value1 | null |
| |------------| +----------------------+
| modulo size | null |
| = offset |------------| +---------------------+
+--------------> | reference | -> | key2 | value2 | ref |
|------------| +---------------------+
| .... | |
+----------------+
V
+----------------------+
| key3 | value3 | null |
+----------------------+
答案 1 :(得分:27)
根据hashmap的源代码,每个hashCode都使用以下方法进行哈希处理:
/**
* Applies a supplemental hash function to a given hashCode, which
* defends against poor quality hash functions. This is critical
* because HashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
每个hashCode再次进行哈希处理的原因是为了进一步防止冲突(参见上面的评论)
HashMap还使用一种方法来确定哈希码的索引(因为长度总是2的幂,你可以使用&而不是%):
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
put方法类似于:
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
哈希码的目的是为给定对象提供唯一的整数表示。那么,有意义的是,Integer的hashCode方法只返回值,因为每个值对于该Integer对象都是唯一的。
答案 2 :(得分:4)
在步骤a。生成与您的密钥对应的整数。这可以在Java中修改。
在步骤b中。 Java应用压缩技术来映射步骤a返回的整数。到hashmap或hashtable中的一个槽。这种压缩技术无法改变。
答案 3 :(得分:0)
我认为这里的概念存在一些混淆。哈希函数将可变大小的输入映射到固定大小的输出(哈希值)。对于Java对象,输出是32位有符号整数。
Java的Hashtable使用哈希值作为存储实际对象的数组的索引,将模数算术和冲突考虑在内。但是,这不是散列。
java.util.HashMap实现在索引之前对哈希值执行一些额外的位交换,以防止在某些情况下发生过多冲突。它被称为“额外哈希”,但我不认为这是一个正确的术语。
答案 4 :(得分:0)
/**
* 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.
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这是java中的hashMap类使用的最新哈希函数
答案 5 :(得分:-1)
以非常简单的方式进行第二次散列只是查找将存储新键值对的bucket数组的索引号。完成此映射以从密钥obj的哈希码的较大int值获得索引号。现在,如果两个不相等的密钥对象具有相同的哈希码,则会发生冲突,因为它们将映射到相同的数组索引。在这种情况下,第二个键及其值将被添加到链接列表中。这里数组索引将指向最后添加的节点。