第一节 参考
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() 获取