以hashmap 源碼爲例, 分析map數據結構,
今天仔細看了一下hashmap的源碼,有hashmap又有了新的認識,先記錄下來,不敢妄談對hashm已經完全瞭解,但求能滴水穿石
無論hashtable 亦或 hashmap 存數map的基本數據結構都是 transient Entry[] table;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
......
}
以前只是以爲,存儲的是一個 Entry 類型的數組,但是 請仔細看 Entry 的結構中有 這樣 一個 Entry<K,V> next;字段,沒錯 這是鏈表結構纔會有的 ,目的是爲了指向 下一個節點。
可是hashmap不是一個數組嗎?怎麼還扯出鏈表結構了呢?繼續看下面的代碼
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;
}
這是hashmap函數的put方法,下面我們來仔細分析一下這段代碼
首先 檢查 key 是否爲null,如果爲null ,則直接執行 putForNullKey函數
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
因爲null的hashcode始終爲0 ,所以 k = null v = valye的這個map一定放在 table的 第 0個 位置(entry 在table中 存放位置 稍後解釋)
注意紅色標記的那句代碼告訴我們,table中每個索引位 存放的就是一個鏈表。
好了,我們回到上面的put方法,接着往下看,
通過key的hashCode()方法計算了這個key的hash值,這個hash值被用來計算存儲Entry對象的數組中的位置。JDK的設計者假設會有一些人可能寫出非常差的hashCode()方法,會出現一些非常大或者非常小的hash值。爲了解決這個問題,他們引入了另外一個hash函數,接受對象的hashCode(),並轉換到適合數組的容量大小。
接着是indexFor(hash,table,length)方法,這個方法計算了entry對象存儲的準確位置。
接下來就是主要的部分,我們都知道兩個不相等的對象可能擁有過相同的hashCode值,兩個不同的對象是怎麼存儲在相同的索引位[叫做bucket]呢?
答案是LinkedList。如果你記得,Entry類有一個next變量,這個變量總是指向鏈中的下一個變量,這完全符合鏈表的特點。
所以,在發生碰撞的時候,entry對象會被以鏈表的形式存儲起來,當一個Entry對象需要被存儲的時候,hashmap檢查該位置是否已近有了一個entry對象,如果沒有就存在那裏,如果有了就檢查她的next屬性,如果是空,當前的entry對象就作爲已經存儲的entry對象的下一個節點,依次類推。
在這種方式下HashMap就能保證key的唯一性。
又上我們可以推斷出 get(key)函數 的 工作原理
先 得到 key 的 hashcode 。然後通過hash函數封裝hashcode得到 int型數據 hash
indexFor 函數 通過hash 得到 entry 在 table中的索引 ,然後開始遍歷 該索引位的 entry鏈表 (遍歷條件,hash值首先要相等,當然得了, key == 或者 equal )
條件符合 則範圍 entey.value
下面就是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;
}
至此,OK,hashmap函數的分析先告一段落,上面的圖像,完完全全的體現出 table 數組存數的entry的存數結構。