- HashMap就是一張hash表,鍵和值都沒有排序。
HashMap是非線程安全的,只用於單線程環境下,多線程環境下可以採用concurrent併發包下的concurrentHashMap。
HashMap 實現了Serializable接口,因此它支持序列化。
HashMap 容量設爲不小於指定容量的2的冪次方,且最大值不能超過2的30次方。
HashMap的存儲結構
紫色部分即代表哈希表本身(其實是一個數組),數組的每個元素都是一個單鏈表的頭節點,鏈表是用來解決hash地址衝突的,如果不同的key映射到了數組的同一位置處,就將其放入單鏈表中保存。
HashMap中put和get的源碼:
- get方法源碼
// 獲取key對應的value
public V get(Object key) {
if (key == null)
return getForNullKey();
// 獲取key的hash值
int hash = hash(key.hashCode());
// 在“該hash值對應的鏈表”上查找“鍵值等於key”的元素
for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
// 判斷key是否相同
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
// 沒找到則返回null
return null;
}
// 獲取“key爲null”的元素的值,HashMap將“key爲null”的元素存儲在table[0]位置,但不一定是該鏈表的第一個位置
private V getForNullKey() {
for (Entry<K, V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
- put方法源碼
// 將“key-value”添加到HashMap中
public V put(K key, V value) {
// 若“key爲null”,則將該鍵值對添加到table[0]中。
if (key == null)
return putForNullKey(value);
// 若“key不爲null”,則計算該key的哈希值,然後將其添加到該哈希值對應的鏈表中。
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;
// 若“該key”對應的鍵值對已經存在,則用新的value取代舊的value。然後退出!
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 若“該key”對應的鍵值對不存在,則將“key-value”添加到table中
modCount++;
// 將key-value添加到table[i]處
addEntry(hash, key, value, i);
return null;
}
put方法源碼
如果key爲null,則將其添加到table[0]對應的鏈表中,如果key不爲null,則同樣先求出key的hash值,
根據hash值得出在table中的索引,而後遍歷對應的單鏈表,如果單鏈表中存在與目標key相等的鍵值對,
則將新的value覆蓋舊的value,且將舊的value返回,如果找不到與目標key相等的鍵值對,或者該單鏈表爲空,
則將該鍵值對插入到單鏈表的頭結點位置(每次新插入的節點都是放在頭結點的位置),
該操作是有addEntry方法實現的,它的源碼如下:
// 新增Entry。將“key-value”插入指定位置,bucketIndex是位置索引。
void addEntry(int hash, K key, V value, int bucketIndex) {
// 保存“bucketIndex”位置的值到“e”中
Entry<K, V> e = table[bucketIndex];
// 設置“bucketIndex”位置的元素爲“新Entry”,
// 設置“e”爲“新Entry的下一個節點”
table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
// 若HashMap的實際大小 不小於 “閾值”,則調整HashMap的大小
if (size++ >= threshold)
resize(2 * table.length);
}
注意這裏倒數第三行的構造方法,將key-value鍵值對賦給table[bucketIndex],並將其next指向元素e,
這便將key-value放到了頭結點中,並將之前的頭結點接在了它的後面。該方法也說明,每次put鍵值對的時候
,總是將新的該鍵值對放在table[bucketIndex]處(即頭結點處)。兩外注意最後兩行代碼,每次加入鍵值對時
,都要判斷當前已用的槽的數目是否大於等於閥值(容量*加載因子),如果大於等於,則進行擴容,將容量擴爲
原來容量的2倍。