java程式設計思想讀書筆記三(HashMap詳解)
Map
Map介面規定了一系列的操作,作為一個總規範它所定義的方法也是最基礎,最通用的。
AbstractMap
AbstractMap是HashMap、TreeMap,、ConcurrentHashMap 等類的父類。當我們巨集觀去理解Map時會發現,其實Map就是一個儲存Entry<K,V>的陣列,AbstractMap類的設計就是用程式碼來描述這句話。
AbstractMap的設計思路是將方法的實現都建立在操作Entry<K,V>陣列上,從而將對Map所有方法的抽象轉變為粒度更小的Entry<K,V>陣列物件的抽象,從而不同的Map實現類只需要簡單的繼承AbstractMap,並且實現entrySet()方法去構造Entry<K,V>陣列,便可以實現一個最簡單的Map了。
//AbstractMap中唯一的抽象方法
public abstract Set<Entry<K,V>> entrySet();
當通過實現entrySet()方法後,構造出自己的Entry集合後,其它常用操作便已經被AbstractMap去實現好了。
//根據key獲取value
public V get(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext ()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;
}
//判斷value是否存在
public boolean containsValue(Object value) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (value==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getValue()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (value.equals(e.getValue()))
return true;
}
}
return false;
}
//判斷key是否存在
public boolean containsKey(Object key) {
Iterator<Map.Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return true;
}
}
return false;
}
AbstractMap作為抽象類的意義就是為了使它將抽象的粒度縮小到最低,低到只對外提供一個抽象方法,而卻將應用範圍擴大,大到成為基本所有Map實現類的父類。
HashMap概述
HashMap是Map介面中最常用的一個實現類了,HashMap的主幹部分是一個數組,每個陣列中存放的都是一個連結串列,當連結串列超過一個設定的閾值時,又會轉變為一個紅黑樹。
HashMap的這種資料結構是為了提高hash衝突多後的查詢效率,我們如果只考慮功能,而不考慮效能、時間複雜度的情況下,去實現一個存放Key,Value的資料結構其實很容易,直接去定義一個Entry陣列即可,這樣我們就實現了Map的功能了,而查詢的時間複雜度也很容易算出來是O(n),顯然這種時間複雜度作為一個優秀程式語言的底層集合容器是不能被接受的,而HashMpa則通過Hash函式直接將Key對映為Value的記憶體地址,從而在近似O(1)的時間內查詢到資料的值。
HashMap中hash函式設計
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
hashMap在hash對映過程中,hashCode的值是一個32位的二進位制數,約為40億,要是直接使用,則需要分配tab的大小為40多億,這顯然是不合理的。
hashMap的容量都是2的n次方,預設值為16,這樣的規則就是為了通過hashMap的容量-1去得出低位掩碼,如16-1為0000000000000000000000000000001111,通過掩碼與hashcode進行與
操作,從而將hash值範圍固定在hashMap的容量以內,使用(h = key.hashCode()) ^ (h >>> 16)這步操作使key的高位低位都參與到運算,避免低位相同高位不同的hashCode產生碰撞。進過上面處理的hash值將能夠儘可能的保證少的碰撞,並且能夠將長度與tab的長度一致。