源碼解析有參考以下部落格:
http://www.cnblogs.com/jzb-blog/p/6637823.html
HashMap:
以k-v鍵值對存儲格式的容器,key,value都可以為空,key不重複,非線程安全(線程安全請使用ConcurrentHashMap);
底層采用的是 數組+(連結清單 / 紅黑樹)結構組成; 常用的有put(),get(),size(),remove(),entrySet()等方法。
下面是對:初始化resize,put,get的源碼解析,都在源碼中每一步中均有注釋說明
初始化resize:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length; // 舊數組大小
int oldThr = threshold; // 舊數組容量
int newCap, newThr = 0; // 新數組大小和容量
// 1.舊數組大小大于0 (新數組大小及容量均擴大兩倍 或 新數組大小擴大兩倍 或 無需擴容)
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) { // 若舊數組大小>=最大容量
threshold = Integer.MAX_VALUE; // 數組容量為Integer最大值
return oldTab;
}
// 新數組大小擴大二倍,
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 若newCap小于最大容量, 舊數組大小>=16 則新容量擴大二倍
newThr = oldThr << 1; // double threshold
}
// 2.舊數組容量大于0 (新數組大小等于舊容量,未涉及新容量)
else if (oldThr > 0) // initial capacity was placed in threshold
// 新大小 = 舊容量
newCap = oldThr;
// 3.使用預設值(新數組大小預設值16,新容量=16*0.75(預設負載因子)=12)
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 若新數組容量=0(1和2 會導緻newThr==0)
if (newThr == 0) {
// 新容量預期值 = 新數組大小*負載因子
float ft = (float)newCap * loadFactor;
// 若新數組大小和ft都小于最大容量 則新容量=ft,否則等于Integer最大值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
// 容量的值 = 新容量
threshold = newThr;
// newCap值為新數組大小建立node數組
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// table引用newTab
table = newTab;
if (oldTab != null) {
// 省略不做分析 :在新數組中添加舊資料
}
}
添加元素put
1.計算hash值并調用putVal方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
2.判斷數組是否為空,如果為空,初始化table數組
3.添加資料
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// tab是指将要操作的數組引用
// p表示tab上hash值對應的Node節點
// n表示tab長度,i表示p在tab下标
Node<K,V>[] tab; Node<K,V> p; int n, i;
// table為存儲資料的數組
if ((tab = table) == null || (n = tab.length) == 0)
// 2.初始化
n = (tab = resize()).length;
// 3.添加資料
// 判斷key所在的Node數組元素p是否為空
if ((p = tab[i = (n - 1) & hash]) == null)
// 3.1 p == null 隻需建立一個Node即可
tab[i] = newNode(hash, key, value, null);
else {
// e: p中node節點(表示每次p.next的Node),最終表示即将添加的key,value
// k: p中node節點(e)的key
HashMap.Node<K,V> e; K k;
// 3.2 p != null
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
// 3.2.1 p第一個節點的key就等于添加的key,直接傳回p
e = p;
else if (p instanceof HashMap.TreeNode)
// 3.2.2 p是紅黑樹----todo
e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 3.2.3 周遊連結清單結構的p,每次擷取p的下一個節點Node,并記錄binCount(标記用于轉換紅黑樹)
for (int binCount = 0; ; ++binCount) {
// 3.2.4 p.next == null
if ((e = p.next) == null) { // p.next 表示擷取p的下一個Node
// 表示已經到達節點末尾,隻需要建立key,value的Node對象,并将p.nexc指向它,退出循環
p.next = newNode(hash, key, value, null);
// 若p節點大于等于8,将連結清單結構轉化為紅黑樹
// (條件比較>=7,因為binCount從0開始計數的) ----todo
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 3.2.5 p.next.key == key (e.key等于添加的key,退出循環)
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
// 重置p,不然的話每次p.next都是p的第二個節點(因為e = p.next,加上p = e,就相當于p = p.next)
p = e;
}
}
// 3.2.6 将value替換e中的value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 這個函數在hashmap中沒有任何操作,是個空函數,他存在主要是為了linkedHashMap的一些後續處理工作。
// 引用http://www.cnblogs.com/jzb-blog/p/6637823.html 原話
afterNodeAccess(e);
return oldValue;
}
}
// 資料結構變化
++modCount;
if (++size > threshold)
// 前面提到過threshold是數組容量 若超過需要擴容
resize();
// 這裡與前面的afterNodeAccess同理,是用于linkedHashMap的尾部操作,HashMap中并無實際意義。
// 引用http://www.cnblogs.com/jzb-blog/p/6637823.html 原話
afterNodeInsertion(evict);
return null;
}
擷取get
通過hash值找到在table數組中的那個Node節點(該節點存有大于等于8個元素,則轉化為紅黑樹,發生在put操作),
紅黑樹:通過樹形結構查找 連結清單:循環比較連結清單每一個Node的key
final HashMap.Node<K,V> getNode(int hash, Object key) {
// table數組 first:hash值對應的Node e:first中的其中一個
// n:tab數組長度 k:first中的key
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k;
// 1.查詢hash值對應的node節點 為null直接傳回null
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 2.該hash位置的Node第一個key等于需要查詢的key 傳回該節點
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 3.擷取first下一個node
if ((e = first.next) != null) {
// 3.1 若是樹 通過樹形結構擷取key對應的Node
if (first instanceof HashMap.TreeNode)
return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);
// 3.2 否則循環比較first連結清單每一個Node的key
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
面試必問的源碼問題,今天抽出時間也把源碼了解以下并釋出文章。希望有描述不準确,了解不到位的地方,
非常感謝各位,能夠指點一下,有您的指點我定會更加深刻,非常感謝花費您寶貴的時間看我這篇文章。
再次注明參考部落格:
http://www.cnblogs.com/jzb-blog/p/6637823.html