Java使用什么散列函数来实现Hashtable类?

时间:2012-02-20 15:57:41

标签: java algorithm hashtable hash

从CLRS(“算法导论”)一书中,有几种散列函数,如mod,multiply等。

Java使用什么散列函数将密钥映射到插槽?

我看到这里有一个问题Hashing function used in Java Language。但它没有回答这个问题,我认为这个问题的明确答案是错误的。它说hashCode()允许你为Hashtable做你自己的散列函数,但我认为这是错误的。

hashCode()返回的整数是Hashtble的真正密钥,然后Hashtable使用散列函数来散列hashCode()。这个答案暗示的是Java让你有机会给Hashtable一个散列函数,但不,这是错误的。 hashCode()给出了真正的密钥,而不是散列函数。

那么Java使用的哈希函数究竟是什么?

6 个答案:

答案 0 :(得分:94)

当在OpenJDK中向HashMap添加或请求密钥时,执行流程如下:

  1. 使用开发人员定义的hashCode()方法将密钥转换为32位值。
  2. 然后通过第二个哈希函数(其中Andrew的答案包含源代码)将32位值转换为哈希表内的偏移量。第二个哈希函数由HashMap的实现提供,并且不能被开发人员覆盖。
  3. 如果哈希表中尚不存在该键,则哈希表的相应条目包含对链接列表的引用或null。如果存在冲突(具有相同偏移的多个键),则将键及其值简单地收集在单个链接列表中。
  4. 如果选择适当高的哈希表大小,则冲突的数量将受到限制。因此,单个查找平均只需要恒定的时间。这称为预期的恒定时间。但是,如果攻击者控制插入哈希表的密钥并了解正在使用的哈希算法,他可能会引发大量哈希冲突,从而迫使线性查找时间。这就是为什么最近一些哈希表实现已被更改为包含一个随机元素,使攻击者更难以预测哪些键会导致冲突。

    一些ASCII艺术

    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)

哈希一般分为两个步骤: 一个。的hashCode 湾压缩

在步骤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值获得索引号。现在,如果两个不相等的密钥对象具有相同的哈希码,则会发生冲突,因为它们将映射到相同的数组索引。在这种情况下,第二个键及其值将被添加到链接列表中。这里数组索引将指向最后添加的节点。