簡體   English   中英

hashCode,實現以及與HashMap的關系

[英]hashCode, implementation, and relation to HashMap

所以我在這里問了另一個相關的問題: 帶有雪崩效應的java字符串哈希函數 ,但我現在有一個不同的相關問題。

我在那個問題中建立的是String的hashCode()函數沒有雪崩效應。 這意味着,例如,如果我有字符串“k1”,“k2”,“k3”,並且我在每個上調用hashCode(),則返回的值將是連續的。

現在,基於我對數據結構101的回憶,我的印象是這是一件壞事。 因為假設HashMap通過算法選擇桶,例如:

class HashMap {
    private int capacity;
    private int chooseBucket(String key) {
        return key.hashCode() % capacity;
    }
}

這意味着類似的密鑰存儲在連續的桶中,導致更高的沖突率,從O(1)降低大O查詢時間到......誰知道有多糟糕......可能比O更差(log n )。

我在第一個問題中得到的答案類型是“這里不需要雪崩效應”,“它僅用於加密哈希函數”,而且“字符串的hashCode實現很快,適用於小型哈希映射”。

這讓我很困惑。 當它們很小時,所有數據結構都很快。 Sun是否會提供一個適用於大型數據集的默認hashCode函數? 那時候HashMap的表現真的很重要,不是嗎?

或者,我錯過了什么? 請賜教。

將密鑰存儲在連續的存儲區中不會導致性能下降。 將密鑰存儲在同一個存儲桶中(例如, 鏈接 )可以。 使用鏈接解決哈希沖突時:

  • 最壞情況:每個哈希值都相同,因此所有元素都在同一個桶中,在這種情況下,您獲得O(n)性能(假設鏈是鏈接列表)
  • 最佳情況:每個哈希值都不同,因此每個元素最終都在不同的存儲桶中,因此您可以獲得預期的O(1)性能。

在哈希表的使用(等)的散列碼並不需要一個雪崩效應

我前幾天閱讀了Eric Lippert的博客文章,題為GetHashCode的指南和規則 雖然代碼示例與C#相關,但大多數一般原則同樣適用於Java。 如果您想了解更多關於使用什么哈希碼以及如何生成哈希碼的信息,那么本文非常值得一讀。

特別是,以下位似乎與您的問題特別相關:

指南:哈希碼的分布必須是“隨機的”

通過“隨機分布”,我的意思是如果被散列的對象中存在共性,則在生成的散列碼中不應存在類似的共性。

你問“或者,我錯過了什么?請賜教。”

是的,你錯過了什么。

在HashMap類實現中,它可以防止糟糕的散列函數:

/**
 * 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);
}

因此,您的示例中生成的哈希碼是:

k1 - Before: 3366 After: 3566
k2 - Before: 3367 After: 3567
k3 - Before: 3368 After: 3552

因此,即使在3個元素的小樣本中,其中一個也被重新加入。 現在,這不能防止惡意的hashCodes( return randomInt();return 4;根本無法保護)但它確實防止寫得不好的哈希碼。

我還應該指出,你可以通過使用非平凡的輸入來改變很多東西。 例如考慮以下字符串。

k1longer - Before: 1237990607 After: 1304548342
k2longer - Before: 2125494288 After: 2040627866
k3longer - Before: -1281969327 After: -1178377711

注意低位有多少不同:對於哈希碼來說,唯一重要的是低位。 支持地圖的大小始終是2的冪。 它實際上在代碼中記錄了這種方式:

/**
 * The table, resized as necessary. Length MUST Always be a power of two.
 */
transient Entry[] table;

重新編寫相當不錯的工作確保高位(通常在散列表中被忽略)仍然對低位有影響。 這是原始哈希碼位置的映射及其影響的位:

00: 00000000000000000000000000000001
01: 00000000000000000000000000000010
02: 00000000000000000000000000000100
03: 00000000000000000000000000001000
04: 00000000000000000000000000010001
05: 00000000000000000000000000100010
06: 00000000000000000000000001000100
07: 00000000000000000000000010001001
08: 00000000000000000000000100010010
09: 00000000000000000000001000100100
10: 00000000000000000000010001001000
11: 00000000000000000000100010010000
12: 00000000000000000001000100100001
13: 00000000000000000010001001000010
14: 00000000000000000100010010000100
15: 00000000000000001000100100001000
16: 00000000000000010001001000010001
17: 00000000000000100010010000100010
18: 00000000000001000100100001000100
19: 00000000000010001001000010001001
20: 00000000000100010010000100010011
21: 00000000001000100100001000100110
22: 00000000010001001000010001001100
23: 00000000100010010000100010011000 # means a 1 in the 23rd bit position will  
24: 00000001000100100001000100110001  # cause positions 4, 5, 8, 12, and 20 to 
25: 00000010001001000010001001100010  # also be altered
26: 00000100010010000100010011000100
27: 00001000100100001000100110001001
28: 00010001001000010001001100010010
29: 00100010010000100010011000100100
30: 01000100100001000100110001001000
31: 10001001000010001001100010010000

所以,你擔心“從O(1)降低大O查詢時間到......誰知道有多糟糕......可能比O(log n)更差”和“Sun不會提供默認的hashCode函數適用於大型數據集嗎?“ 可能會被擱置 - 他們有安全措施來防止這種情況發生。

如果它可以幫助你獲得一些平靜,這里是這個類的作者標簽。 他們幾乎都是Java世界的明星。 (評論#是我的)

 * @author  Doug Lea          # Formerly a Java Community Process Executive Committee member
 * @author  Josh Bloch        # Chief Java architect at Google, amongst other things
 * @author  Arthur van Hoff   # Done too many hardcore Java things to list...
 * @author  Neal Gafter       # Now a lead on the C# team at Microsoft, used to be team lead on javac 

像HashMap這樣的哈希函數的散列函數需要對它的鍵集合理唯一,但鍵之間的關系(即兩個鍵的相似程度)不一定是隨機的。 我們真正想要避免的是在一個桶中的一堆對象會使搜索該桶變得昂貴。

在HashMaps和Strings的情況下,它必須將這些散列鍵映射到一個隨機可訪問容器的某個排序偏移量,例如有許多解決方案的數組,但如果兩個鍵“關閉”,它仍將導致它們放在不同的水桶里,這是我們真正關心的。

對於非常大的Map容器​​(想想數十億個鍵),我們可能確實想要更聰明一點,但這似乎超出了Java的HashMap設計的范圍。

最后要注意的是,您不必使用雪崩效果為字符串生成相當隨機的鍵。 您希望選擇一個足夠隨機且盡可能快的函數。

如果您查看HashMap的源代碼,則會使用key.hashCode()值調用哈希函數,這意味着它會通過自己的方式分配哈希值。 有一點需要確定的是不遵守equals和hashcode合同。 我建議如果你正在尋找性能改進來查看源代碼並了解可用桶的數量和它的最佳用途。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM