[英]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.