Map是廣義java集合框架中的另外一部分,HaspMap作爲框架中使用頻率最高類型之一,我們有必要去深入瞭解它。
對比 HashMap,HashTable,TreeMap
相同點
- HashMap,HashTable,TreeMap都是一些常見的map實現,是以鍵值對的形式存儲和操作數據的容器類型。
不同點
- HashTable是早期的Java類庫提供的的一個哈希表實現,本身是同步的,不支持null鍵和空值,由於同步導致的性能開銷,使用相當較少。
- HashMap是應用更加廣泛的哈希表實現,行爲上大致和haspTable大致相同,主要區別在於HashMap不是同步的,支持null鍵和空值。所以性能上更加優秀。
- TreeMap是基於紅黑樹的一種提供順序訪問的Map,和HashMap不同,他的get,put,remove之類的操作都是O(log(n))的時間複雜度,具體順序可以由指定的Comparator來決定,或者根據鍵的自然順序來判斷。
HashMap常見問題
haspMap明確聲明不是線程安全的的數據結構,所以haspMap在併發環境可能出現無限循環佔用cpu,size不準確的詭異問題。
知識擴展
map結構
hashMap等其他map實現都hi擴展abstractMap,裏面包含了通用的抽象方法,HashMap的性能表現非常依賴於哈希碼的有效性。
- equals相等,hashcode一定要相等。
- 重寫了hashCode也要重寫equals。
- hashCode需要保持一致性,狀態改變的哈希值仍然要一致。
- equals的對稱,反射,傳遞。
LinkedHashMap 和 TreeMap 都可以保證某種順序,但二者還是很不同的
-
linkedHashMap通常提供的是遍歷順序符合插入順序,他的實現是通過條目維護一個雙向鏈表。
-
對於TreeMap他的整體順序是由鍵的順序關係決定的,通過compartoar來決定。
HashMap源碼分析 -
HashMap 內部實現基本點分析。
-
容量(capacity)和負載係數(load factor)。
-
樹化
HashMap的內部結構,可以看作是數組和鏈表組成的複合結構,數組被分成一個個桶數組,通過哈希值在這個桶數組尋址,哈希值相同的鍵值對,則以鏈表形式存儲。
從非拷貝構造函數的實現來看,這個表格(數組)似乎並沒有在最初就初始化好,僅僅設置了一些初始值而已。
public HashMap(int initialCapacity, float loadFactor){
// ...
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
然後看put方法實現
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put裏面調用了putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbent,
boolean evit) {
Node<K,V>[] tab; Node<K,V> p; int , i;
if ((tab = table) == null || (n = tab.length) = 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == ull)
tab[i] = newNode(hash, key, value, nll);
else {
// ...
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for first
treeifyBin(tab, hash);
// ...
}
}
從源碼種可以看出
如果表格是null,resize會初始化它
resize方法兼顧兩個職責,創建初始存儲表格,或者在容量不滿足需求的時候進行擴容
在放置新的鍵值對的時候,如果發生以下情況,就會發生擴容
if (++size > threshold)
resize();
具體鍵值在哈希表種的位置取決於下面的運算
i = (n - 1) & hash
最後hashMap爲什麼要樹化,本質上是安全問題。因爲在元素放置過程中,如果一個對象的哈希衝突,都被放置在一個桶中,會形成一個鏈表,鏈表查詢是線性的會嚴重影響存取的性能。