Java HashMap的工作原理 及各種Map區別
阿新 • • 發佈:2019-01-05
一、Java HashMap的工作原理
面試的時候經常會遇見諸如:“java中的HashMap是怎麼工作的”,“HashMap的get和put內部的工作原理”這樣的問題。
Put :
讓我們看下put方法的實現:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<k , V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
現在我們一步一步來看下上面的程式碼。
1).對key做null檢查。如果key是null,會被儲存到table[0],因為null的hash值總是0。
2).key的hashcode()方法會被呼叫(hashCode方法的定義用到了native關鍵字,表示它是由C或C++採用較為底層的方式來實現的,你可以認為它返回了該物件的記憶體地址;而預設equals
則認為,只有當兩者引用同一個物件時,才認為它們是相等的。),然後計算hash值。hash值用來找到儲存Entry物件的陣列的索引。有時候hash函式可能寫的很不好,所以JDK的設計者新增
了另一個叫做hash()的方法,它接收剛才計算的hash值作為引數。如果你想了解更多關於hash()函式的東西,可以參考:hashmap中的hash和indexFor方法
3).indexFor(hash,table.length)用來計算在table陣列中儲存Entry物件的精確的索引。
4)如果兩個key有相同的hash值(也叫衝突),他們會以連結串列的形式來儲存。所以,這裡我們就迭代連結串列。用當前Entry的value來替換之前的value。
Get:
現在我們來看下get方法的實現:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<k , V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
當你傳遞一個key從hashmap獲取value的時候:
1).對key進行null檢查。如果key是null,table[0]這個位置的元素將被返回。
2).key的hashcode()方法被呼叫,然後計算hash值。
3).indexFor(hash,table.length)用來計算要獲取的Entry物件在table陣列中的精確的位置,使用剛才計算的hash值。
4).在獲取了table陣列的索引之後,會迭代連結串列,呼叫equals()方法檢查key的相等性,如果equals()方法返回true,get方法返回Entry物件的value,否則,返回null。
要牢記以下關鍵點:
1)HashMap有一個叫做Entry的內部類,它用來儲存key-value對。
2)上面的Entry物件是儲存在一個叫做table的Entry陣列中。
3)table的索引在邏輯上叫做“桶”(bucket),它儲存了連結串列的第一個元素。
4)key的hashcode()方法用來找到Entry物件所在的桶。
5)如果兩個key有相同的hash值,他們會被放在table陣列的同一個桶裡面。
6)key的equals()方法用來確保key的唯一性。
7)value物件的equals()和hashcode()方法根本一點用也沒有。
二、Hashtable、LinkedHashMap、TreeMap、SortedMap、WeakHashMap、IdentityHashMap、ConcurrentHashMap的區別:
(1) HashMap與HashTable的區別:
a.Hashtable中的物件是執行緒安全的。而HashMap則是非同步的,因此HashMap中的物件並不是執行緒安全的。因為同步的要求會影響執行的效率,所以如果你不需要執行緒安全的集合那麼使用
HashMap是一個很好的選擇,這樣可以避免由於同步帶來的不必要的效能開銷,從而提高效率。
b.值:HashMap可以讓你將空值作為一個表的條目的key或value,但是Hashtable是不能放入空值的。HashMap最多隻有一個key值為null,但可以有無數多個value值為null。
注意:
1、用作key的物件必須實現hashCode和equals方法。
2、不能保證其中的鍵值對的順序
3、儘量不要使用可變物件作為它們的key值。
(2) LinkedHashMap:
它的父類是HashMap,使用雙向連結串列來維護鍵值對的次序,迭代順序與鍵值對的插入順序保持一致。LinkedHashMap需要維護元素的插入順序,插入效能略低於HashMap,但在迭代訪問元
素時有很好的效能,因為它是以連結串列來維護內部順序。
(3) TreeMap和SortedMap:
Map介面派生了一個SortMap子介面,SortMap的實現類為TreeMap。TreeMap也是基於紅黑樹對所有的key進行排序,有兩種排序方式:自然排序和定製排序。HashMap通常比TreeMap快一點(樹
和雜湊表的資料結構使然),建議多使用HashMap,在需要排序的Map時候才用TreeMap。
(4) WeakHashMap:
WeakHashMap與HashMap的用法基本相同,區別在於:後者的key保留物件的強引用,即只要HashMap物件不被銷燬,其物件所有key所引用的物件不會被垃圾回收;WeakHashMap適合短時間內就
過期的快取時最好使用weakHashMap,它包含了一個自動呼叫的方法expungeStaleEntries,這樣就會在值被引用後直接執行這個隱含的方法,將不用的鍵清除掉。
(5) IdentityHashMap類:
IdentityHashMap與HashMap基本相似,只是當兩個key嚴格相等時,即key1==key2時,它才認為兩個key是相等的 。IdentityHashMap也允許使用null,但不保證鍵值對之間的順序。
(6) EnumMap類:
1、EnumMap中所有key都必須是單個列舉類的列舉值,建立EnumMap時必須顯示或隱式指定它對應的列舉類。
2、EnumMap根據key的自然順序,即列舉值在列舉類中定義的順序,來維護鍵值對的次序。
3、EnumMap不允許使用null作為key值,但value可以。
(7) ConcurrentHashMap:
1.ConcurrentHashMap對整個桶陣列進行了分段,而HashMap則沒有
2.ConcurrentHashMap在每一個分段上都用鎖進行保護,從而讓鎖的粒度更精細一些,併發效能更好,而HashMap沒有鎖機制,不是執行緒安全的。
三、紅黑樹的理解?
紅黑樹是一種自平衡二叉查詢樹,紅黑樹是一種很有意思的平衡檢索樹;每次插入的時候都要進行計算,保證二叉樹的平衡;如果有2的N次方資料量級,查詢的時候只需要查詢N次即可。
我們對任何有效的紅黑樹加以如下增補要求:
1.節點是紅色或黑色。
2.根是黑色。
3.所有葉子(外部節點)都是黑色。
4.每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
5.從每個葉子到根的所有路徑都包含相同數目的黑色節點。
這些約束強制了紅黑樹的關鍵屬性: 從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這個樹大致上是平衡的。
面試的時候經常會遇見諸如:“java中的HashMap是怎麼工作的”,“HashMap的get和put內部的工作原理”這樣的問題。
Put :
讓我們看下put方法的實現:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<k , V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
現在我們一步一步來看下上面的程式碼。
1).對key做null檢查。如果key是null,會被儲存到table[0],因為null的hash值總是0。
2).key的hashcode()方法會被呼叫(hashCode方法的定義用到了native關鍵字,表示它是由C或C++採用較為底層的方式來實現的,你可以認為它返回了該物件的記憶體地址;而預設equals
則認為,只有當兩者引用同一個物件時,才認為它們是相等的。),然後計算hash值。hash值用來找到儲存Entry物件的陣列的索引。有時候hash函式可能寫的很不好,所以JDK的設計者新增
了另一個叫做hash()的方法,它接收剛才計算的hash值作為引數。如果你想了解更多關於hash()函式的東西,可以參考:hashmap中的hash和indexFor方法
3).indexFor(hash,table.length)用來計算在table陣列中儲存Entry物件的精確的索引。
4)如果兩個key有相同的hash值(也叫衝突),他們會以連結串列的形式來儲存。所以,這裡我們就迭代連結串列。用當前Entry的value來替換之前的value。
Get:
現在我們來看下get方法的實現:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<k , V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
當你傳遞一個key從hashmap獲取value的時候:
1).對key進行null檢查。如果key是null,table[0]這個位置的元素將被返回。
2).key的hashcode()方法被呼叫,然後計算hash值。
3).indexFor(hash,table.length)用來計算要獲取的Entry物件在table陣列中的精確的位置,使用剛才計算的hash值。
4).在獲取了table陣列的索引之後,會迭代連結串列,呼叫equals()方法檢查key的相等性,如果equals()方法返回true,get方法返回Entry物件的value,否則,返回null。
要牢記以下關鍵點:
1)HashMap有一個叫做Entry的內部類,它用來儲存key-value對。
2)上面的Entry物件是儲存在一個叫做table的Entry陣列中。
3)table的索引在邏輯上叫做“桶”(bucket),它儲存了連結串列的第一個元素。
4)key的hashcode()方法用來找到Entry物件所在的桶。
5)如果兩個key有相同的hash值,他們會被放在table陣列的同一個桶裡面。
6)key的equals()方法用來確保key的唯一性。
7)value物件的equals()和hashcode()方法根本一點用也沒有。
二、Hashtable、LinkedHashMap、TreeMap、SortedMap、WeakHashMap、IdentityHashMap、ConcurrentHashMap的區別:
(1) HashMap與HashTable的區別:
a.Hashtable中的物件是執行緒安全的。而HashMap則是非同步的,因此HashMap中的物件並不是執行緒安全的。因為同步的要求會影響執行的效率,所以如果你不需要執行緒安全的集合那麼使用
HashMap是一個很好的選擇,這樣可以避免由於同步帶來的不必要的效能開銷,從而提高效率。
b.值:HashMap可以讓你將空值作為一個表的條目的key或value,但是Hashtable是不能放入空值的。HashMap最多隻有一個key值為null,但可以有無數多個value值為null。
注意:
1、用作key的物件必須實現hashCode和equals方法。
2、不能保證其中的鍵值對的順序
3、儘量不要使用可變物件作為它們的key值。
(2) LinkedHashMap:
它的父類是HashMap,使用雙向連結串列來維護鍵值對的次序,迭代順序與鍵值對的插入順序保持一致。LinkedHashMap需要維護元素的插入順序,插入效能略低於HashMap,但在迭代訪問元
素時有很好的效能,因為它是以連結串列來維護內部順序。
(3) TreeMap和SortedMap:
Map介面派生了一個SortMap子介面,SortMap的實現類為TreeMap。TreeMap也是基於紅黑樹對所有的key進行排序,有兩種排序方式:自然排序和定製排序。HashMap通常比TreeMap快一點(樹
和雜湊表的資料結構使然),建議多使用HashMap,在需要排序的Map時候才用TreeMap。
(4) WeakHashMap:
WeakHashMap與HashMap的用法基本相同,區別在於:後者的key保留物件的強引用,即只要HashMap物件不被銷燬,其物件所有key所引用的物件不會被垃圾回收;WeakHashMap適合短時間內就
過期的快取時最好使用weakHashMap,它包含了一個自動呼叫的方法expungeStaleEntries,這樣就會在值被引用後直接執行這個隱含的方法,將不用的鍵清除掉。
(5) IdentityHashMap類:
IdentityHashMap與HashMap基本相似,只是當兩個key嚴格相等時,即key1==key2時,它才認為兩個key是相等的 。IdentityHashMap也允許使用null,但不保證鍵值對之間的順序。
(6) EnumMap類:
1、EnumMap中所有key都必須是單個列舉類的列舉值,建立EnumMap時必須顯示或隱式指定它對應的列舉類。
2、EnumMap根據key的自然順序,即列舉值在列舉類中定義的順序,來維護鍵值對的次序。
3、EnumMap不允許使用null作為key值,但value可以。
(7) ConcurrentHashMap:
1.ConcurrentHashMap對整個桶陣列進行了分段,而HashMap則沒有
2.ConcurrentHashMap在每一個分段上都用鎖進行保護,從而讓鎖的粒度更精細一些,併發效能更好,而HashMap沒有鎖機制,不是執行緒安全的。
三、紅黑樹的理解?
紅黑樹是一種自平衡二叉查詢樹,紅黑樹是一種很有意思的平衡檢索樹;每次插入的時候都要進行計算,保證二叉樹的平衡;如果有2的N次方資料量級,查詢的時候只需要查詢N次即可。
我們對任何有效的紅黑樹加以如下增補要求:
1.節點是紅色或黑色。
2.根是黑色。
3.所有葉子(外部節點)都是黑色。
4.每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
5.從每個葉子到根的所有路徑都包含相同數目的黑色節點。
這些約束強制了紅黑樹的關鍵屬性: 從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這個樹大致上是平衡的。