簡體   English   中英

如果keySet()保持HashMap的順序,為什么我們需要LinkedHashMap?

[英]Why do we need LinkedHashMap if keySet() maintains order for a HashMap?

public class HashMapKeySet {

public static void main(String[] args) {
    Map<HashCodeSame,Boolean> map=new HashMap();

    map.put(new HashCodeSame(10),true);
    map.put(new HashCodeSame(2),false);

    for(HashCodeSame i:map.keySet())
        System.out.println("Key: "+i+"\t Key Value: "+i.getA()+"\t Value: "+map.get(i)+"\t Hashcode: "+i
                .hashCode());

    System.out.println("\nEntry Set******");
    for(Map.Entry<HashCodeSame, Boolean> i:map.entrySet())
        System.out.println("Key: "+i.getKey().getA()+"\t Value: "+i.getValue()+"\t Hashcode: "+i.hashCode());

    System.out.println("\nValues******");
    for(Boolean i:map.values())
        System.out.println("Key: "+i+"\t Value: "+map.get(i)+"\t Hashcode: "+i.hashCode());

}

static class HashCodeSame{

    private int a;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    HashCodeSame(int a){
        this.a=a;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        HashCodeSame that = (HashCodeSame) o;

        return a == that.a;

    }

    @Override
    public int hashCode() {
        return 1;
    }
}

}

如果您可以在上面的示例中看到,我已經明確地使hashcode()在所有情況下都返回1,以檢查在hashmap中的key.hashcode()發生沖突時發生了什么。 發生什么情況,將為這些Map.Entry對象維護一個鏈接列表,例如

1(key.hashcode())將鏈接到<2,false>將鏈接到<10,true>

(據我所知,在正確值之后輸入了錯誤值)。

但是當我執行keySet()時,將首先返回true,然后返回false,而不是首先返回false。

所以,我在這里假設的是,由於keySet()是一個集合,並且集合保持順序,因此在迭代時會得到true和false。 但是,話又說回來,為什么我們不說hashmap保持順序,因為檢索的唯一方法是按順序進行。 或為什么我們使用LinkedHashMap?

 Key: DS.HashMapKeySet$HashCodeSame@1    Key Value: 10   Value: true     Hashcode: 1
Key: DS.HashMapKeySet$HashCodeSame@1     Key Value: 2    Value: false    Hashcode: 1

Entry Set******
Key: 10  Value: true     Hashcode: 1230
Key: 2   Value: false    Hashcode: 1236

Values******
Key: true    Value: null     Hashcode: 1231
Key: false   Value: null     Hashcode: 1237

現在,當我添加chsnge時,hashcode方法返回一個like

@Override
    public int hashCode() {
        return a;
    }

我得到相反的順序。 再加上

    map.put(new HashCodeSame(10),true);
    map.put(new HashCodeSame(2),false);
    map.put(new HashCodeSame(7),false);
    map.put(new HashCodeSame(3),true);
    map.put(new HashCodeSame(9),true);

收到的輸出是

    Key: DS.HashMapKeySet$HashCodeSame@2     Key Value: 2    Value: false    Hashcode: 2
Key: DS.HashMapKeySet$HashCodeSame@3     Key Value: 3    Value: false    Hashcode: 3
Key: DS.HashMapKeySet$HashCodeSame@7     Key Value: 7    Value: false    Hashcode: 7
Key: DS.HashMapKeySet$HashCodeSame@9     Key Value: 9    Value: true     Hashcode: 9
Key: DS.HashMapKeySet$HashCodeSame@a     Key Value: 10   Value: true     Hashcode: 10

Entry Set******
Key: 2   Value: false    Hashcode: 1239
Key: 3   Value: false    Hashcode: 1238
Key: 7   Value: false    Hashcode: 1234
Key: 9   Value: true     Hashcode: 1222
Key: 10  Value: true     Hashcode: 1221

Values******
Key: false   Value: null     Hashcode: 1237
Key: false   Value: null     Hashcode: 1237
Key: false   Value: null     Hashcode: 1237
Key: true    Value: null     Hashcode: 1231
Key: true    Value: null     Hashcode: 1231

現在,這又再次讓我感到奇怪,為什么訂單以排序的方式出現? 誰能詳細解釋一下hashmap的keySet(),entrySet()方法如何工作?

HashMap 沒有定義的迭代順序,而LinkedHashMap 有指定的迭代順序。

HashMap的困難在於,即使無法保證迭代順序的可預測性和穩定性,也很容易構造簡單的示例。

例如,假設您這樣做:

    Map<String, Boolean> map = new HashMap<>();
    String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    for (int i = 0; i < str.length(); i++) {
        map.put(str.substring(i, i+1), true);
    }
    System.out.println(map.keySet());

結果是

[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]

嘿! 那些是有秩序的! 好吧,原因是String的hashCode()函數非常糟糕,而對於單字符字符串則特別糟糕。 這是String的hashCode()規范 本質上,它是一個求和乘,但對於單字符字符串,它只是該char的Unicode值。 所以,上面的單字符字符串的哈希碼是65,66,90 ...的內部表HashMap始終是2的冪,在這種情況下它的64項長。 使用的表條目是鍵的hashCode()值,該值右移16位並與自身進行異或,以表大小為模。 請參見此處的代碼 。)因此,這些單字符字符串最終在HashMap表中的順序存儲區中,位於數組位置1、2,... 26中。

密鑰迭代順序地在存儲桶中進行,因此密鑰最終以與輸入時相同的順序出現。同樣,不能保證,由於各種實現的特性,它恰好以這種方式工作如上所述。

現在考慮HashCodeSame ,其中hashCode()函數每次都返回1。 將這些對象中的一些添加到HashMap中將導致它們全部都位於同一存儲桶中,並且由於迭代順序地遍歷鏈表,因此它們將按順序出現:

    Map<HashCodeSame, Boolean> map = new HashMap<>();
    for (int i = 0; i < 8; i++) {
        map.put(new HashCodeSame(i), true);
    }
    System.out.println(map.keySet());

(我添加了一個顯而易見的事情toString()方法。)結果是:

[HCS(0), HCS(1), HCS(2), HCS(3), HCS(4), HCS(5), HCS(6), HCS(7)]

同樣,由於實現的巧合,密鑰是按順序列出的,但是出於與上述不同的原因。

可是等等! 在JDK 8中,如果同一存儲桶中有太多條目,則HashMap會將存儲桶從線性鏈接列表轉換為平衡樹。 如果同一存儲桶中有8個以上的條目,則會發生這種情況。 讓我們嘗試一下:

    Map<HashCodeSame, Boolean> map = new HashMap<>();
    for (int i = 0; i < 20; i++) {
        map.put(new HashCodeSame(i), true);
    }
    System.out.println(map.keySet());

結果是:

[HCS(5), HCS(0), HCS(1), HCS(2), HCS(3), HCS(4), HCS(6),
HCS(18), HCS(7), HCS(11), HCS(16), HCS(17), HCS(15), HCS(13),
HCS(14), HCS(8), HCS(12), HCS(9), HCS(10), HCS(19)]

底線是HashMap 維護定義的迭代順序。 如果需要特定的迭代順序,則必須使用LinkedHashMap或排序的映射,例如TreeMap 不幸的是, HashMap的迭代順序是相當穩定且可預測的,事實上,它只是可預測的,足以使人們認為其順序是明確定義的,而實際上卻並非如此。


為了幫助解決此問題,在JDK 9中,新的基於哈希的collection實現將在運行之間將其迭代順序隨機化 例如:

    Set<String> set = Set.of("A", "B", "C", "D", "E",
                             "F", "G", "H", "I", "J");
    System.out.println(set);

在不同的JVM調用中運行時,這會打印出以下內容:

[I, H, J, A, C, B, E, D, G, F]
[C, B, A, G, F, E, D, J, I, H]
[A, B, C, H, I, J, D, E, F, G]

(迭代順序在JVM的一次運行中是穩定的。而且,諸如HashMap現有集合不會將其迭代順序隨機化。)

在Java文檔中為LinkedHashMap回答您的問題

Map接口的哈希表和鏈表實現,具有可預測的迭代順序。 此實現與HashMap的不同之處在於,它維護貫穿其所有條目的雙向鏈接列表。 此鏈表定義了迭代順序,通常是將鍵插入映射的順序(插入順序)。 請注意,如果將密鑰重新插入到映射中,則插入順序不會受到影響。 (如果在調用之前m.containsKey(k)將立即返回true時調用了m.put(k,v),則將密鑰k重新插入到映射m中。)

此實現使客戶免於HashMap(和Hashtable)提供的未指定的,通常混亂的排序,而不會增加與TreeMap相關的成本。 無論原始地圖的實現如何,都可以使用它來生成與原始地圖具有相同順序的地圖副本:

 void foo(Map m) {
     Map copy = new LinkedHashMap(m);
     ...
 }

暫無
暫無

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

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