第一節 參考
1. https://segmentfault.com/a/1190000012728513
第二節 架構
一.關注點
1.jdk1.7以前使用數組,鏈表實現.1.8之後使用數組,鏈表,紅黑樹.
2.threshold=capacity * load factor. 當size個數到達threshold而且目標hash值初不爲空時,做resize擴容,擴容2倍.
3.鏈表頭插法,擴容後鏈表倒置
4.取模通過h & (length - 1).
5.length取大於長度的2的冪次方的數值,算法是位操作,取出最高位的1的位置,較低的位全是1.
6.索引值。取低位的哈希值,調用 key & (length - 1)
7.重新hash基本不會觸發,需要元素個數大於Inter.Max_value.
8.多線程擴容死循環問題.
9.因爲沒有重新算hash值,同一hash值的鏈表還是在擴容之後的數組的同一位置的鏈表處,只是鏈表倒置.
10.modcount引起的concurrentModifyException異常.
11.jdk1.8上,如果節點個數大於TREEIFY_THRESHOLD,單鏈錶轉爲紅黑樹存儲或者拆鏈表擴容,TREEIFY_THRESHOLD默認爲8.
二.紅黑樹
(一).性質
二叉查找樹的一種,避免普通二叉查找樹有序時退化成單鏈表
1.節點是紅色或者黑色
2.根節點是黑色
3.每個葉子節點(NIL)是黑色
4.紅色節點的兩個子節點是黑色
5.任一節點到每個葉子都有相同數目的黑色節點.
(二).插入節點
1.1.8之後的紅黑樹左旋,右旋三步走.
左旋(左邊少樹):頂點往左下來,右邊子節點上去做根,右邊子節點的左子樹左旋掛到原來頂點的右子樹上。
右旋(右邊少樹):頂點往右下來,左邊子節點上去做根,左邊子節點的右子樹右旋掛到原來頂點的左子樹上.
2.新插入節點默認爲紅色。如果插入黑色節點,當前路徑比其他路徑多出黑色節點,調整麻煩.
插入場景分別如下:
a.如果只有一個節點,即根節點黑色.如果只有兩個或者三個節點,根節點黑色,子節點紅色.
b.紅黑樹變色.條件:新插入節點的父節點和叔叔節點是紅色.
變色:父節點和叔叔節點變黑色,爺爺節點變紅色.
c.紅黑樹左旋.條件:不滿足規則的連續紅色節點的下面節點是右邊子節點,它的父親是紅色,叔叔是黑色.
左旋:不滿足規則的連續紅色節點的上面節點作爲頂點,左旋.左旋不變色.
d.紅黑樹右旋.條件:不滿足規則的連續紅色節點的下面節點是左邊子節點,它的父親是紅色,叔叔是黑色.
右旋:不滿足規則的連續紅色節點的上面節點的父節點作爲頂點,即下面節點的爺爺節點作爲頂點,右旋.右旋需要多一個變色操作,
父節點右旋上去後變黑,爺爺節點右旋下來後變紅.
(三).刪除節點
第三節 源碼細節
一.構造方法
如果HashMap()使用默認的空構造方法,只設置loadFactor爲0.75.閾值和容量都爲0.
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
初始化初始容量(默認16),裝載因子(默認0.75),閾值(大於閾值時擴容2倍,默認16*0.75=12).
tableSizeFor方法用於返回大於容量的2次冪的值
默認的空構造方法在第一次put的resize時設置這些值.
二.put() 插入數據
hash是key的hashCode()方法值異或hashcode的高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)
//第一次時table爲空,創建table的node數組,
n = (tab = resize()).length;
//索引i即是(n-1)&hash
if ((p = tab[i = (n - 1) & hash]) == null)
//看數組的該索引處是否爲空,如果爲空,創建單鏈表,
//這裏即爲單鏈表的第一個節點
tab[i] = newNode(hash, key, value, null);
else {
//該索引處已經有值,產生碰撞時,進入這裏.此時,p指向鏈表第一個節點
Node<K,V> e; K k;
//如果第一個節點的key,hash都和put值相等,代表直接value在後面放在第一個節點裏面
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) {
//p向後遍歷,直到最後一個節點
if ((e = p.next) == null) {
//把插入的節點放在最後一個節點後面
p.next = newNode(hash, key, value, null);
//binCount從0個數,這裏單鏈表節點個數從第9個開始,轉爲紅黑樹存儲或者擴容拆鏈表,
//即鏈表最多長度爲9,因爲上面一行已經把第9個節點放到鏈表尾部
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果遍歷過程中有節點和插入的值hash,key都相等,跳出循環
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果已經存在key,hash相同,則指向已有節點,此時e不爲空.不存在已有節點,則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;
}
三.resize() 擴容
如果HashMap()使用默認的空構造方法時,第一次put進入這裏創建node數組.
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//原來容量已經超過最大整數,基本不可能
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//正常的擴容操作,原來的threshold默認是12,變成24,cap是16,變成32.
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else {
// zero initial threshold signifies using defaults
//使用默認的空構造方法時,容量設置爲16.閾值爲12.
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
//創建節點數組,默認大小爲16.第一個次擴容在前面變成32.
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
//初始化設置不進這裏.第一次擴容時進入這裏,依次移動原來數組中的每個單鏈表
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//原數組中該索引處有元素,開始移動
if ((e = oldTab[j]) != null) {
//原來數組該索引處清空,節點賦給e
oldTab[j] = null;
if (e.next == null)
//原數組該索引處單鏈表只有一個節點,根據hash值和新的cap容量重新計算節點索引位置
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
//原數組該索引處是紅黑樹,進行分裂操作
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
//第一次擴容,拆分鏈表進這裏
//單鏈表按照元素排序的奇偶分成兩個鏈表,
//loHead指向奇數位置鏈表.hiHead指向偶數位置鏈表
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
//e每次指向新遍歷的節點
next = e.next;
//鏈接奇數鏈表
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
//鏈接偶數鏈表
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
//奇數鏈表的索引不變
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
//偶數鏈表的索引變成原索引+原數組容量16
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
四.Node節點及單鏈表
每個節點存儲key,value,下一個節點,hash值.
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
...
}
五.treeifyBin() 紅黑樹存儲
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
//第一次進來treeifyBin方法就擴容,n是數組默認大小16,小於64,不轉紅黑樹
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
//數組長度擴容爲64時才變成紅黑樹,map內存儲元素個數爲33
TreeNode<K,V> hd = null, tl = null;
do {
//e指向單鏈表第一個結點
//單鏈表每個節點創建一個TreeNode,填充hash,key,value,next
TreeNode<K,V> 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);
}
}
六.單鏈錶轉紅黑樹方法
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
//x指向當前節點,先把左右子樹置null
x.left = x.right = null;
//創建紅黑樹根節點
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
//下插入的hash值比父節點大,插入到右子樹上
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
//dir = 1時,插入到右子樹.dir = -1是插入到左子樹
//p根據遍歷的當前節點的值,指向父節點的左子樹或者右子樹
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
//插入後做二叉樹的平衡(變色,左右旋)
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
七.樹節點結構
class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
...
}
八.紅黑樹的平衡
<K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true;
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
//只有一個節點,可能是紅色,變成黑色
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//只有一個黑色節點
else if (!xp.red || (xpp = xp.parent) == null)
return root;
if (xp == (xppl = xpp.left)) {
//參考鏈接1的情況三
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.right) {
//參考鏈接1的情況四
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
}
else {
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
九.左旋
參考鏈接1的情況四
左旋三步走:右孩子的左子樹掛到父節點的右子樹上,右孩子左旋上移,父節點左旋向左下方下移
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
//p是情況四的P節點,r是N節點.即p是連續紅色節點的上面的節點,r是連續紅色的右孩子節點
TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) {
//右孩子r的左子樹掛到父節點p的右子樹上
if ((rl = p.right = r.left) != null)
//r的左子樹的父節點指向p
rl.parent = p;
//r節點左旋上移,即r的父節點指向爺爺節點
if ((pp = r.parent = p.parent) == null)
//如果爺爺節點爲空,則r變爲根節點,需要變成黑色
(root = r).red = false;
else if (pp.left == p)
//爺爺節點的左子樹指向r節點
pp.left = r;
else
//爺爺節點的左子樹指向r節點
pp.right = r;
//r節點的左子樹指向原來的父節點,即原來的父節點向左下旋轉移動
r.left = p;
p.parent = r;
}
return root;
}
十.右旋
參考鏈接1的情況五
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
//p是情況五的G節點,l是P節點.即p是連續紅色節點的上面紅色節點的父節點,即爺爺節點,l是連續紅色節點的上面的節點
TreeNode<K,V> l, pp, lr;
if (p != null && (l = p.left) != null) {
//爺爺節點的左子樹指向父節點的右子樹,即父親節點的右子樹右旋掛到爺爺節點的左子樹上.
if ((lr = p.left = l.right) != null)
//父節點的右子樹指向爺爺節點
lr.parent = p;
//爺爺節點如果是null節點,父節點作爲根節點變成黑色
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
//父節點的右子樹指向爺爺節點
l.right = p;
//爺爺節點的父節點指向父節點
p.parent = l;
}
return root;
}
十一.get() 獲取