Map接口結構
map接口是一個雙邊隊列,擁有key,value兩個屬性,其中key在存儲的集合中不允許重復,value可以重復。
MapHashMapLinkedHashMapHashtable實現map接口實現map接口繼承HashMap實現map接口MapHashMapLinkedHashMapHashtable
HashMap特點
存儲結構在jdk1.7當中是數組加鏈表的結構,在jdk1.8當中改為了數組加鏈表加紅黑樹的結構。
HashMap在多線程的環境下是不安全的,沒有進行加鎖措施,所以執行效率快。如果我么需要有一個線程安全的HashMap,可以使用Collections.synchronizedMap(Map m)方法獲得線程安全的HashMap,也可以使用ConcurrentHashMap類創建線程安全的map。
存儲的元素在jdk1.7當中是Entry作為存儲的節點,在jdk1.8當中用Node作為存儲的節點,Node實現了Map.Entry接口。在map中其實是將key和value節點組合起來成為一個Node節點作為存儲。
// jdk1.8Node節點
static class Node implements Map.Entry {
final int hash;
final K key;
V value;
Node next;
Node(int hash, K key, V value, Node next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
// 下面省略代碼
}
在HashMap中key不允許重復,value可以允許重復,並且key和value都允許是null值,因為他們是另外存儲的。
當我們使用自定義類作為HashMap的key時我們需要重寫object類的,hashCode()和equals()方法。
在HashMap中鏈表部分在jdk1.7中新的節點總是在鏈表的頭部,舊的節點在新節點的next域當中,而在jdk1.8中是新的節點總是在鏈表的尾部,他們的指向都是由舊節點指向新的節點,由此我們可以記成七上八下。
HashMap當中常見的名詞
DEFAULT_INITIAL_CAPACITY 默認數組容量大小,16
MAXIMUM_CAPACITY 最大數組容量大小,2^30
DEFAULT_LOAD_FACTOR 默認的負載因子,0.75
TREEIFY_THRESHOLD 鏈表改成紅黑樹存儲的最小長度
MIN_TREEIFY_CAPACITY 鏈表改成紅黑樹存儲,map數組最小容量
HashMap存儲元素過程源碼解析(jdk1.8)
// 存儲元素的數組,加上transient關鍵字代表不可以被序列化
transient Node[] table;
// 這個方法是HashMap對外公開的添加元素方法
public V put(K key, V value) {
// 實際調用里面的一個默認修飾符的方法
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
// 在jdk1.7當中,創建一個HashMap實例是直接分配一個長度為16的數組,
// 在jdk1.8當中,創建HashMap時是分配了一個長度為0的數組,然后調用put方法時,如果數組長
// 度為0時,則調用數組擴容方法,擴容數組到長度為16。當長度不為0時,擴容操作是擴大到原來
// 長度的兩倍
if ((tab = table) == null || (n = tab.length) == 0)
// 調用擴容方法,並利用n變量,記錄數組的長度
n = (tab = resize()).length;
// 通過計算哈希散列,得出該key應該放在數組的哪一個位置上,用變量p存儲該位置上的元素
// 判斷該位置上是否已經存在元素,如果不存在元素則直接將該元素放入數組中,存放
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 如果數組的該位置已經存在了元素
else {
Node e; K k;
// 比較需要加入的元素,於原來的元素hash值進行比較,如果hash值和equals都為true那么
// 判定兩個元素為相同元素,用變量e來記錄原來的元素,方便下面進行替換
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果上述條件不滿足,但是是一個TreeNode類型,則當作樹節點插入
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {鄭州哪家人流醫院好 http://www.zzzyrl120.com/
// 當hash值不同,或者equals為false,也不是TreeNode時將新元素插入數組對應位置的
// 鏈表中,遍歷對應數組位置的鏈表
for (int binCount = 0; ; ++binCount) {
// 用變量e存儲鏈表的下一個節點
if ((e = p.next) == null) {
// 如果該鏈表只有一個元素,則直接將舊節點的next域指向新的節點
p.next = newNode(hash, key, value, null);
// 當單條鏈表上元素數量大於最大數量時則按照紅黑樹存儲8,在進行紅黑樹時
// 又會進行判斷數組容量是否到達紅黑樹最小容量64,兩個條件同時滿足,則該
// 條鏈表改造成紅黑樹存儲
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 比較鏈表的下一個節點的hash值和equals
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果e不等於null則證明,在鏈表中存在相同的元素,則進行替換
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;
}
// 將鏈表改造成紅黑樹
final void treeifyBin(Node[] tab, int hash) {
int n, index; Node e;
// 判斷是否滿足條件,數組大小達到64,沒有達到則做擴容操作,達到則改成紅黑樹
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode hd = null, tl = null;
do {
TreeNode p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
HashMap常見的問題
為什么在使用自定義類型作為key需要我們重寫hashCode()和equals()方法
因為map的存值的時候是先計算hash值,然后再判斷equals,通過這兩個值是否都為true來判斷該元素是否再map中已經存在。如果不重寫這兩個方法,可能會存在我們認為相同,但是他們的hash或者equals不同的元素也能存進map中,從而達不到key唯一的效果。
在HashMap中為什么要存在DEFAULT_LOAD_FACTOR負載因子這個概念
因為我們map的存儲數據結構是數組加鏈表加紅黑樹結構,如果沒有負載因子,map是不可能滿的,所以加上一個負載因子的概念來判斷數組是否擴容,減少鏈表的負擔。