HashMap在jdk1.8是数组+链表+红黑树实现,当链表的节点树大于等于8的时候,会把链表转成红黑树,
当触发扩容的时候,会把树变成2棵树,如果树的节点不够UNTREEIFY_THRESHOLD = 6,就会退化成链表
允许存放null值,也可以插入null值
扩容机制是
默认增长的长度是 旧长度 << 1 也就是是原来的2倍
默认长度是 1 << 4
默认的load_factor = 0.75f
阀值 threshold = 长度 * load_factor 下一次扩容的阀值
是先插入在扩容,还是扩容再插入?
在第一次插入的时候进行初始化,会执行初始化数组,和分配新的threshold,之后是插入再判断 size > threshold
put(k key,V value) 插入元素
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
//h >>> 16 获取 key.hashCode()高位16位
// key.hashCode() 异或高位16位,让hash分布均衡
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//这里的主要逻辑
//先判断是否是第一次进这个方法,判断条件(tab = table) == null || (n = tab.length)
//如果是 则需要初始化table,执行resize()进行初始化
//如果不是第一次进,那根据(n - 1) & hash公式,计算出需要插入到table数组的哪一个位置
//如果计算出来的位置是个空位置,则直接tab[i] = newNode(hash, key, value, null)
//如果计算出来的位置不是一个空值(这里用p来表示这个不是空值的元素)
//如果p元素的hash值和key值都和插入的元素一样,则说明插入的元素已经存在,可能只是值不一样
//如果p元素的hash值和key值和插入的元素不一样,则需要判断这个p是否是树元素
//如果p是个树元素,则((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value),进行红黑树插入
//如果p元素的hash值和key值和插入的元素不一样,p又不是一个树元素,则只能是链表元素了
//如果p元素是链表元素
//那就循环p对应的链表,直到找到一个空的位置
//如果循环的次数,已经大于等于7,刚好找到位置,
//也就是准备插入第8个元素的时候,会先把需要插入的元素插入链表 p.next = newNode(hash, key, value, null)
//然后进行链表转换成红黑树 treeifyBin(tab, hash); 结束循环
//如果循坏的过程中找到一个节点的ash值和key值和插入的元素一样
//则直接结束循坏,说明链表里已经存在需要插入的元素,只是值可能不一样
//最后在上面的操作后,是找到的元素,就会根据条件 if (!onlyIfAbsent || oldValue == null)
//put的时候 onlyIfAbsent=false //如果为true,则不更改现有值
// 把需要插入的元素的值覆盖找到元素的值即可
//忘了,size++ modCount++ 当然还需要判断是否需要扩容 if (++size > threshold) resize()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; //hashmap存放元素的array
Node<K,V> p; //指的是通过key的hashcode计算出来的tab数组下标对于的node
int n;//这里指的是tab的长度
int i;
if ((tab = table) == null || (n = tab.length) == 0) //如果是第一次put,会进行初始化
n = (tab = resize()).length; //tab = resize()进行初始化
//获取这个key的可以保存的数组下标,如果这个这个位置为空,说明没人占
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);//直接创建newNode,放进tab[i]
else { //获取的这个位置有人占
Node<K,V> e;
K k; //插的key
//如果占位置的节点的key和插入的key的一样,后面就只需要改这个节点的值就行
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) {
//如果占位置的节点的下一个位置为空
//记得e = p.next,已经被赋值
if ((e = p.next) == null) { //p的下一个节点是个空节点,那就直接插入
//直接把数据放进这个空的位置,也即是p.next,后插法
p.next = newNode(hash, key, value, null);
//TREEIFY_THRESHOLD = 8
//binCount是0开始的 所以TREEIFY_THRESHOLD - 1
//也就是插入第8个的时候
//当链表的元素大于等于TREEIFY_THRESHOLD,treeifyBin(tab, hash)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//满足树形化容器要求最小值64
treeifyBin(tab, hash);
break;//推出循环
}
//如果在链表里面找到,则只需要改值
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e; //把游标指向p的下一个元素
}
}
//说明存在旧值的key与要插入的key"相等"
if (e != null) { // existing mapping for key
V oldValue = e.value;
//是允许覆盖值,oldValue==null,设置值
//因为e可能已存在的,所以设置旧值即可
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);//没有实现
return oldValue;
}
}
++modCount;//修改次数+1
if (++size > threshold) //判断是否需要扩容
resize();//扩容
afterNodeInsertion(evict);//没有实现
return null;
}
//put的时候 root.putTreeVal(this, tab, hash, key, value)
//这里意思是
//从根节点开始循环
//根据节点的hash值大小,判断需要插入的位置,
//如果hash值一样,则判断是否key一样,key一样直接返回(插入的值已经存在)
///如果hash值一样,key不一样,根据key的comparableTo比较大小,
//没有实现comparable ,如果是第一次进来,会遍历整一个树,判断需要插入的节点是否已存在
//最后使用System.identityHashCode(a)进行比较,
//System.identityHashCode(a) 无论给定对象的类是否覆盖hashCode(),
//返回给定对象与默认方法hashCode()返回的相同哈希代码。空引用的哈希码为零
//最后根据比较的结果dir来决定,当dir<=0 p = p.left 否则p = p.left
//如果p==null 则说明有位置插入,这里就是进行x = map.newTreeNode(h, k, v, xpn),然后就是关系绑定
// TreeNode<K,V> xp = p; Node<K,V> xpn = xp.next x = map.newTreeNode(h, k, v, xpn)
//如此绑定关系
//先确定树的关系 dir<=0? xp.left = x: xp.right = x
//后确定双向链表的关系 xp->x->xpn xp<-x<-xpn
//如果p!=null 说明没有插入的位置,继续循坏查找
//返回值插入返回null 找到返回找到的节点
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
TreeNode<K,V> root = (parent != null) ? root() : this; //获取根元素
for (TreeNode<K,V> p = root;;) {
int dir;
int ph; // p.hash
int K;
int pk;//p,key
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;//put进来的元素,已经在树里面找到,直接返回
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
//获取不到k的比较器
if (!searched) { //是否已搜索
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q; //从p节点遍历完左右节点,发现了
}
dir = tieBreakOrder(k, pk); //获取内存地址比较返回的值
}
TreeNode<K,V> xp = p;
//p = p.left or p.right
//根据dir,需要插入到左边还是右边,p的左边和右边是否有位置,没有继续循环
if ((p = (dir <= 0) ? p.left : p.right) == null) { //新的节点,可以插入的位置
// 上面 TreeNode<K,V> xp = p; p = p.left or p.right
//xpn = xp.next
Node<K,V> xpn = xp.next;
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
xp.left = x; //小于在左
else
xp.right = x;//大于在右
//修复关系 xp->x->xpn xp<-x<-xpn
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
//修复树 balanceInsertion
//调整根元素,如果根元素改变,数组保存新的根元素moveRootToFront
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
//寻找策略
//先根据节点的hash值进行比较
//如果hash值一样,比较key值是否相等
//如果hash值一样,key值不想等,那就判断是否是单子节点,p = 单子节点,继续寻找
//如果hash值一样,key值不想等,两个子节点,那就根据两个子节点的key进行比较,要求key实现了comparable
//如果hash值一样,key值不想等,两个子节点,key没有实现comparable,那就先递归查找右边子节点树,找不到则寻找左节点
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q;
if ((ph = p.hash) > h) //大于找左边
p = pl;
else if (ph < h) //小于找右边
p = pr;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p; //找到就返回
//p.hash == h 是等于的 ,那就判断是否是单子节点,继续循环
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
//p.hash == h 是等于的 两个子节点
//如果key实现了comparable接口或者是string ,Integer等实现了comparable的自然顺序的key,
//则按照comparable比较大小进行左右的选择
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
//p.hash == h 是等于的 两个子节点
//key没有实现comparable接口或者不是是string ,Integer等实现了comparable的自然顺序的key
//递归查找右边
else if ((q = pr.find(h, k, kc)) != null)
return q;
else //如果右边找不到,只能找左边,继续找
p = pl; //左边
} while (p != null);
return null;
}
//把链表变成红黑树
//tab =hashmap存放元素的array
//hash =key计算出来的hash
//主要逻辑
//tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY
//先判断tab数组有没有初始化或者长度64不够64,没满足条件则初始化或者扩容 resize()
//根据index = (n - 1) & hash 计算出hash 存放到tab数组的下标,然后取出 e = tab[index]
//如果e不等于空 则遍历e对应的链表
//遍历中把链表Node 变成 TreeNode TreeNode<K,V> p = replacementTreeNode(e, null)
//最后以链表的第一个元素调用 treeify(tab);
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n;//等于数组长度
int index; //hash 对于tab数组的下标
Node<K,V> e;//hash的头部节点
//tab 还没初始化,或者初始化了,但是长度不满足树形化容器要求最小值
//MIN_TREEIFY_CAPACITY = 64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();//扩容
//n = tab.length
//tab[index = (n - 1) & hash] 取出这个hash的头部节点
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null;//头节点
TreeNode<K,V> tl = null;//子节点
do {
//拷贝头部元素的属性,创建成TreeNode
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);//遍历链表
//头部元素设置为hd 不等于null==》 hd.treeify(tab);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
//创建
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
return new TreeNode<>(p.hash, p.key, p.value, next);
}
//tab =hashmap存放元素的array
//this = hd 头部元素,现在只是一个双向链表
//这里的逻辑是遍历链表
//如果没有根元素,那么设置根元素 root == null x.parent = null; x.red = false; root = x;
//如果存在根节点,那么就遍历树,从根元素开始
//先根据节点的hash值进行比较
//如果hash值一样,比较key值是否相等
///如果hash值一样,根据key的comparableTo比较大小,
//如果key没有没有实现comparable,最后使用System.identityHashCode()进行比较,
//System.identityHashCode(a) 无论给定对象的类是否覆盖hashCode(),
//返回给定对象与默认方法hashCode()返回的相同哈希代码。空引用的哈希码为零
//最后根据比较的值 绑定关系 dir <= 0 ? p.left : p.right
// TreeNode<K,V> xp = p; x.parent = xp; dir <= 0 ? xp.left = x :xp.right = x;
//绑定关系后就需要保证红黑树没有违反规则,则调用balanceInsertion
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
//x = hd
//next = x.next
//遍历的是列表
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
//左右节点都是null
x.left = x.right = null;
//如果不存在根节点
if (root == null) {
x.parent = null; //x的parent设置为空
x.red = false; //红黑树的根节点是黑色。
root = x; //设置x 为根节点
}
else { //如果存在根节点,也即是不是根节点怎么放
K k = x.key; //取出需要处理的节点x的key
int h = x.hash;///取出需要处理的节点x的hash
Class<?> kc = null; //
//死循环
//TreeNode<K,V> p = root
//遍历的是树呢
for (TreeNode<K,V> p = root;;) {
int dir; //记录p节点和x节点的hash值比较状态 大于-1 小于1
int ph; //节点hsah值
K pk = p.key; //取出节点的key
if ((ph = p.hash) > h) //p节点的hash值> x节点的hash值
dir = -1; //大于设置状态-1
else if (ph < h) //p节点的hash值 < x节点的hash值
dir = 1; //小于设置状态-1
//这里就是等于的意思了
//kc = comparableClassFor(k) ==>x的class
//dir = compareComparables(kc, k, pk) ==>x,p的比较值
//kc = null,
//comparableClassFor(k),获取x的key的类型,
//如果是实现了comparable的就返回x的key的类型,否则返回null,见下面函数分析
//compareComparables,通过 comparable 获取比较的值,没有实现comparable,
//或者p的key的class和x的class 不一样,返回0,见下面函数分析
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
//使用默认的hashcode进行相减,见下面函数分析
dir = tieBreakOrder(k, pk);
//xp = 节点p
TreeNode<K,V> xp = p;
//p > x = -1
//p = 大于 p.left 小于p.right
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//记得xp等于之前的p 进来if p= p.left or p.right 设置x的爸爸是xp
x.parent = xp;
if (dir <= 0) // p > x = -1
xp.left = x; //大于是左边
else
xp.right = x;//小于是右边
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
//如果x的Class是" Class C implements Comparable "的形式,则返回x的Class,否则为null。
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) { //是否实现了Comparable
Class<?> c; //x的class 对象
Type[] ts, as;
Type t;
ParameterizedType p;
//如果是字符串
if ((c = x.getClass()) == String.class) // bypass checks
return c; //返回字符串的class
// c.getGenericInterfaces()对象表示的类或接口直接实现的接口的{@code Type}
//主要是判断c实现的Comparable的泛型类型是否是c的类
if ((ts = c.getGenericInterfaces()) != null) {
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
//kc =null
//k x的key
//x p的key
//比较k x key的值通过 Comparable的compareTo
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
}
//a 指的是 x的key
//b 指的是 p的key
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
//通过x的key的class的类名,string.compareTo(p的key的class的类名)
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
//调用System.identityHashCode(a)
//,System.identityHashCode方法是java根据对象在内存中的地址算出来的一个数值,
//不同的地址算出来的结果是不一样的,跟默认的方法 hashCode() 返回的代码
//根据上面定义的 p>x = -1
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}
//插入平衡
//root 数节点
//x x节点
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true; //插入节点设置为红色,这样不会违法红黑树的,每条路径的黑色节点数都是一样的
//xp x.parent
//xpp xp.parent
//xppl xpp.left
// xppr xpp.right
//从x节点,不断的循环到root接点
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
//x是根节点
if ((xp = x.parent) == null) { //说明是根节点
x.red = false; //红黑树根节点是黑色。
return x; //直接返回根节点
}
//
//x的父节点是黑色 xpp = xp的父节点(x的爷爷是null)
//或者xp就是根节点,或者xp的颜色是黑色,就直接返回来
else if (!xp.red || (xpp = xp.parent) == null)
return root;
//x的父亲 == x的爷爷的左儿子
if (xp == (xppl = xpp.left)) {
//x的爷爷有右儿子,也即是x的叔叔节点,x的叔叔节点是红色
//红黑树插入情景1 x的父节点p是个红色,x的叔叔节点sib是红色
//处理是设置p,sib的颜色为黑色,x的爷爷节点Y设置为黑色,把当前的节点X = 爷爷节点Y 继续循环
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;//把叔叔节点改成黑色
xp.red = false; //父节点改成黑色
xpp.red = true; //爷爷节点改成和红色
x = xpp; //把x变成爷爷
}
//没有叔叔节点,或者叔叔节点是黑色
else {
//红黑树插入情景2 如果x是右节点,x的父节点是红色,叔叔节点是黑色,或者没有叔叔节点
//处理是 把x= x. parent 然后以x. parent进行左旋
if (x == xp.right) {
//左旋,看下面分析了
//x = xp 把x变成x的父亲
//左旋之后xp就变成了x的左节点了
root = rotateLeft(root, x = xp);
//之前x = xp 把x变成x的父亲
//(xp = x.parent) == null 相等说明 x(一开始的x) 的父亲就是根节点
//xpp = xp.parent xpp = x(一开始的x)的祖父
//xp = x.parent xp不可能等于null,他等于之前x
xpp = (xp = x.parent) == null ? null : xp.parent;
}
//情景3 如果x是左节点,x的父节点是红色,x的叔叔节点是黑色,或者没有叔叔节点
//处理是 让x的父节点设置为黑色,x的爷爷节点设置为红色,右旋
//,xp也不是null 是红色
if (xp != null) {
xp.red = false; //变成黑色
if (xpp != null) { //祖父也不是空的
xpp.red = true; //变成红色
//以祖父进行右旋
root = rotateRight(root, xpp);
}
}
}
}
//x的父亲 == x的爷爷的右儿子,镜像的
else {
//x爷爷有左儿子(叔叔节点)并且它还是红色
if (xppl != null && xppl.red) {
xppl.red = false; //左叔叔变成黑色
xp.red = false;//爸爸变成黑色
xpp.red = true;//父亲红色
x = xpp;//把x 变成爷爷节点
}
//x爷爷没有左儿子(叔叔节点)或者 左叔叔节点是黑色
else {
//如果x是父节点的左节点,三点一条线,需要平衡
if (x == xp.left) {
//以父节点右旋一下
//x = xp
root = rotateRight(root, x = xp);
//(xp = x.parent) == null 相等说明 x(一开始的x) 的父亲就是根节点
//xpp = xp.parent xpp = x(一开始的x)的祖父
xpp = (xp = x.parent) == null ? null : xp.parent;
}
//x(一开始的x)的爷爷不是空的
if (xp != null) {
xp.red = false;///x(一开始的x)的爷爷设置为黑色
if (xpp != null) { //x(一开始的x)的祖父不等于null
xpp.red = true; //x(一开始的x)的祖父设置为红色
//左旋
root = rotateLeft(root, xpp);
}
}
}
}
}
}
//左旋
//根节点
//p节点
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> r; //p节点的右节点 r = p.right
TreeNode<K,V> pp;
TreeNode<K,V> rl;
//r = p.right
if (p != null && (r = p.right) != null) {
//rl = p.right = r.left
//p的左孙子也是p的右儿子
//r1 等于 p的左孙子
if ((rl = p.right = r.left) != null)
rl.parent = p; //p的左孙子的爸爸变成p
//p的父亲也是r的父亲
//pp = p的父亲
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;//如果pp==null,那就说明r就是root了,root节点为黑色
else if (pp.left == p) //把r变成p的左儿子
pp.left = r;
else
pp.right = r; ////把r变成p的右儿子
r.left = p;
p.parent = r;
}
return root;
}
//右旋,看下面的图理解
//主要逻辑就是 l的右节点lr成为p的左节点 l成为p的父节点, p成为l的右节点
//先判断p节点 和 p节点的左节点l不能是空的 p != null && (l = p.left) != null
//处理p跟lr的关系
//lr = p.left = l.right; lr.parent = p; l的右节点成为的做节点
//处理p.parent跟l的关系
//pp = l.parent = p.parent == null 说明 l就是根节点 所以设置为黑色
//pp.right = l; pp.right = l;
//处理l跟p的关系
// 主要逻辑 l.right = p; p.parent = l; p成为l的右子节点
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> l, pp, lr;
//l = p.left
if (p != null && (l = p.left) != null) {
//(lr = p.left = l.right
if ((lr = p.left = l.right) != null)
lr.parent = p;
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;
}
//tab 数组
//这里的逻辑是
//如果 root != null && tab != null && (n = tab.length) > 0
//根据 (n - 1) & root.hash 计算出存放这个hash的tab的下标,根据下标取出这个元素first
//如果元素first 不等于传进来的root,那就说明树的根节点因为删除和调整已经发生改变,需要修复
//主要是修复关系
//举例子 1->2->3->4->5 原来tab[i] = 1 传进来的root = 3
//那么先让tab[i] = 3
//然后调整链表的位置 需要调整的位置 2->3->4 记得是双向的 2<-3<-4 变成 1->2->4->5 1<-2<-4<-5
//最后 3->1->2->4->5 记得是双向的3<-1<-2<-4<-5
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;//等于长度
if (root != null && tab != null && (n = tab.length) > 0) {
int index = (n - 1) & root.hash;//计算出头节点的下标
TreeNode<K,V> first = (TreeNode<K,V>)tab[index]; //取出tab中的根元素
if (root != first) {//判断根元素有没有改变
Node<K,V> rn;
tab[index] = root;//设置新的根节点
TreeNode<K,V> rp = root.prev; //根节点的上一个节点,链表
//rn = root.next
if ((rn = root.next) != null)//说明新的根节点的下一个节点不是null
((TreeNode<K,V>)rn).prev = rp; //rp ->rn rp<-rn
if (rp != null)
rp.next = rn; //rp ->rn
if (first != null)//
first.prev = root; //first的上一个是root root<-first
root.next = first; //下一个节点是first root->first
root.prev = null; //根节点的上一个节点肯定是null的
}
assert checkInvariants(root);//断然检测
}
}
//检测树是否正确
static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
tb = t.prev, tn = (TreeNode<K,V>)t.next;
if (tb != null && tb.next != t)
return false;
if (tn != null && tn.prev != t)
return false;
if (tp != null && t != tp.left && t != tp.right)
return false;
if (tl != null && (tl.parent != t || tl.hash > t.hash))
return false;
if (tr != null && (tr.parent != t || tr.hash < t.hash))
return false;
if (t.red && tl != null && tl.red && tr != null && tr.red) //三个红色
return false;
if (tl != null && !checkInvariants(tl))
return false;
if (tr != null && !checkInvariants(tr))
return false;
return true;
}
get(key) 根据key 获取值
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab;
Node<K,V> first;
Node<K,V> e;
int n;
K k;
//first = tab[(n - 1) & hash] 找到根节点
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//如果查找的就是根节点,那就是first,返回first
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//
if ((e = first.next) != null) {
if (first instanceof TreeNode) //是树节点,则按照树节点的方式查找
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//遍历链表
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
//根据hash和key获取树节点
//逻辑是parent== null 就是根 this 不是 root()
final TreeNode<K,V> getTreeNode(int h, Object k) {
//parent是空 root() 否则this
return ((parent != null) ? root() : this).find(h, k, null);
}
//this 就是 调getTreeNode的节点
//逻辑就是一直找p.parent 知道p.parent== null
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
//p = r.parent
if ((p = r.parent) == null)
return r; //r就是根节点
r = p; //继续找
}
}
//寻找策略
//先根据节点的hash值进行比较
//如果hash值一样,比较key值是否相等
//如果hash值一样,key值不想等,那就判断是否是单子节点,p = 单子节点,继续寻找
//如果hash值一样,key值不想等,两个子节点,那就根据两个子节点的key进行比较,要求key实现了comparable
//如果hash值一样,key值不想等,两个子节点,key没有实现comparable,那就先递归查找右边子节点树,找不到则寻找左节点
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q;
if ((ph = p.hash) > h) //大于找左边
p = pl;
else if (ph < h) //小于找右边
p = pr;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p; //找到就返回
//p.hash == h 是等于的 ,那就判断是否是单子节点,继续循环
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
//p.hash == h 是等于的 两个子节点
//如果key实现了comparable接口或者是string ,Integer等实现了comparable的自然顺序的key,
//则按照comparable比较大小进行左右的选择
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
//p.hash == h 是等于的 两个子节点
//key没有实现comparable接口或者不是是string ,Integer等实现了comparable的自然顺序的key
//递归查找右边
else if ((q = pr.find(h, k, kc)) != null)
return q;
else //如果右边找不到,只能找左边,继续找
p = pl; //左边
} while (p != null);
return null;
}
remove(key) 根据key 删除值
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
//删除
//hash 需要删除的hash
//需要删除key
//value //matchValue 为ture=时使用
//matchValue 如果为true,只在值相等时删除 false
//movable 如果为false,则在移除时不移动其他节点 true
//这个方法逻辑是
//(tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null
//上面的条件都要成立才可以玩
//需要删除的元素的hash 和key 是否和 p = tab[index = (n - 1) & hash] 是一样的?
//如果是 那p就是需要删除的元素,node = p;
//如果不是,继续判断 p 是否是树元素
//如果p 是树元素 node = ((TreeNode<K,V>)p).getTreeNode(hash, key); 根据红黑树去找
//如果p 不是树元素,那就是链表元素了,循坏变量p对应的链表,找出hash 和key和需要删除的元素一样的节点 node = e;
//如果 node != null && !matchValue // remove的时候matchValue 是false
//如果成立 先判断找到的节点node是否是树节点 ,如果是 ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable)
//如果找到的节点node是根节点 tab[index] = node.next;//链表节点没有设置 prev ,所以不设置
//如果找到的节点node既不是树节点,也不是根节点,那就是链表节点 p.next = node.next; //链表节点没有设置 prev ,所以不设置
//
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab;
Node<K,V> p;
int n;
int index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;//根节点
//e = p.next
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
//是树节点
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
//链表类型
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e; //找到
break;//退出
}
p = e;//p 等于e的上一个元素
} while ((e = e.next) != null);
}
}
//如果为true,只在值相等时删除
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
//红黑树删除节点
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p) //根节点
tab[index] = node.next; 设置根节点为node的下一个
else
p.next = node.next; //p->node的下一个元素
++modCount;//修改次数+1
--size;//元素-1
afterNodeRemoval(node);//没有实现
return node; //返回需要删除的节点
}
}
return null;
}
//根据hash和key获取树节点
//逻辑是parent== null 就是根 this 不是 root()
final TreeNode<K,V> getTreeNode(int h, Object k) {
//parent是空 root() 否则this
return ((parent != null) ? root() : this).find(h, k, null);
}
//this 就是 调getTreeNode的节点
//逻辑就是一直找p.parent 知道p.parent== null
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
//p = r.parent
if ((p = r.parent) == null)
return r; //r就是根节点
r = p; //继续找
}
}
//寻找策略
//先根据节点的hash值进行比较
//如果hash值一样,比较key值是否相等
//如果hash值一样,key值不想等,那就判断是否是单子节点,p = 单子节点,继续寻找
//如果hash值一样,key值不想等,两个子节点,那就根据两个子节点的key进行比较,要求key实现了comparable
//如果hash值一样,key值不想等,两个子节点,key没有实现comparable,那就先递归查找右边子节点树,找不到则寻找左节点
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q;
if ((ph = p.hash) > h) //大于找左边
p = pl;
else if (ph < h) //小于找右边
p = pr;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p; //找到就返回
//p.hash == h 是等于的 ,那就判断是否是单子节点,继续循环
else if (pl == null)
p = pr;
else if (pr == null)
p = pl;
//p.hash == h 是等于的 两个子节点
//如果key实现了comparable接口或者是string ,Integer等实现了comparable的自然顺序的key,
//则按照comparable比较大小进行左右的选择
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
//p.hash == h 是等于的 两个子节点
//key没有实现comparable接口或者不是是string ,Integer等实现了comparable的自然顺序的key
//递归查找右边
else if ((q = pr.find(h, k, kc)) != null)
return q;
else //如果右边找不到,只能找左边,继续找
p = pl; //左边
} while (p != null);
return null;
}
//movable 如果为false,则在移除时不移动其他节点
//这里的逻辑是
//先判断 tab == null || (n = tab.length) == 0 //tab 不是空的,或者长度大于0
//设置关系 pred->node->succ 改成 pred->succ pred<-succ 链表阶段的删除操作 next,prev
//没有根节点,或者根节点右节点是null,左节点是null 左节点的左节点是null
//直接退化成为链表 first.untreeify(map)
//如果删除节点有两个节点
//第一步,首先判断这个节点是否有右节点
//因为能进这个if肯定是有右节点的
//循环它的右子节点的左节点,直到最后一个左节点就是这个节点的后继节点 while ((sl = s.left) != null)
//最后是调整关系,把p和s的位置调换,如果s 存在右节点那replacement = p的右节点sr,否则replacement=p
//单节点 replacement = pl; replacement = pr;
//没有节点 replacement = p;
//如果是有节点的 就要先p.left = p.right = p.parent = null; 再balanceDeletion
//没节点的balanceDeletion 再 p.parent = null; pp.left = null; pp.right = null;
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
int n;
if (tab == null || (n = tab.length) == 0) //tab 不是空的,或者长度大于0
return;
int index = (n - 1) & hash;//删除元素的hash
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];//根节点
TreeNode<K,V> root = first;
TreeNode<K,V> rl;
TreeNode<K,V> succ = (TreeNode<K,V>)next, //删除节点的下一个
TreeNode<K,V> pred = prev;//删除节点的上一个
//-------------------------------链表阶段的删除操作--------------------------------
//设置关系 pred->node->succ 改成 pred->succ pred<-succ
if (pred == null)//没有上一个节点,那就是根节点
tab[index] = first = succ;//那就是根节点的下一个节点成为根节点
else
pred.next = succ; //删除节点的上一个的下一个节点=删除节点的下一个
if (succ != null) //删除节点的下一个不是空
succ.prev = pred; //删除节点的下一个的上一个元素为删除节点的上一个
//-------------------------------删除操作--------------------------------
//根元素是空直接返回
if (first == null)
return;
//如果root.parent不是空,那就找根元素
if (root.parent != null)
root = root.root();
//没有根节点,或者根节点右节点是null,左节点是null 左节点的左节点是null
//直接退化成为链表 first.untreeify(map)
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
tab[index] = first.untreeify(map); // too small //设置新的根节点
return;//返回
}
//
TreeNode<K,V> p = this; //需要删除的节点
TreeNode<K,V> pl = left;//需要删除的节点的左节点
TreeNode<K,V> pr = right;//需要删除的节点的右节点
TreeNode<K,V> replacement;//替换删除的元素
if (pl != null && pr != null) {
//存在两个子节点
//二叉树的后继节点的寻找方法
//第一步,首先判断这个节点是否有右节点
//情景1 :存在右节点,那么它的后继节点就是它的右子树的最小值,
//循环它的右子节点的左节点,直到最后一个左节点就是这个节点的后继节点
//不存在右节点,那么就要根据这个节点和父节点的关系来确定了
//情景2 :如果,这个节点是父节点的左子节点,那么这个节点的后继节点就是父节点
//情景3 :如果,这个节点是父节点的右节点,那么就要向上循环这个节点的祖先节点,直到找到一个祖先节点是个左节点,
//那么这个左节点的父节点就是这个节点的后续节点
//这个if里面,只符合情景1
//第一步,首先判断这个节点是否有右节点
//因为能进这个if肯定是有右节点的
//循环它的右子节点的左节点,直到最后一个左节点就是这个节点的后继节点 while ((sl = s.left) != null)
//最后是调整关系,把p和s的位置调换,如果s 存在右节点那replacement = p的右节点sr,否则replacement=p
TreeNode<K,V> s = pr;
TreeNode<K,V> sl;
while ((sl = s.left) != null) //s =p的右边树节点,最小的那一个节点
s = sl;
boolean c = s.red; s.red = p.red; p.red = c; // swap colors //p和后续节点s交换颜色
TreeNode<K,V> sr = s.right; //后续节点的右节点
TreeNode<K,V> pp = p.parent; //p的父节点
if (s == pr) { // s 就是 p的右节点
//p.left = p.right = p.parent
p.parent = s; //p.parent = s
s.right = p; //s.right = p
}
else { //s 不是 p的右节点
//绑定p.parent = sp 也就是s的parent
TreeNode<K,V> sp = s.parent; //sp = 后续节点的父节点
if ((p.parent = sp) != null) { //p.parent = sp
if (s == sp.left) //左节点
sp.left = p;
else
sp.right = p; //右节点
}
//绑定s.right
if ((s.right = pr) != null)
pr.parent = s;
}
//这里绑定 p的left 和 p的left
p.left = null;
if ((p.right = sr) != null)
sr.parent = p;
//绑定s.left
if ((s.left = pl) != null)
pl.parent = s;
//这里绑定 s.parent 是 pp(p.parent)
if ((s.parent = pp) == null)
root = s;
else if (p == pp.left)
pp.left = s;
else
pp.right = s;
//如果后续节点有一个右节点
if (sr != null)
replacement = sr; //replacement就是右节点
else
replacement = p; //replacement后续节点
}
//单节点,p的左节点不是空,则replacement就是左节点
else if (pl != null)
replacement = pl;
//单节点,p的右节点不是空,则replacement就是右节点
else if (pr != null)
replacement = pr;
//没有子节点,那replacement就是自己了
else
replacement = p;
if (replacement != p) { //不是没有子节点节点的
//replacement替换掉p
TreeNode<K,V> pp = replacement.parent = p.parent;
if (pp == null)
root = replacement; //直接就是根节点
else if (p == pp.left) //p是左元素,则replacement也是左元素
pp.left = replacement;
else
pp.right = replacement; //p是右元素,则replacement也是右元素
//清除掉p //prev next 上面已经删掉
p.left = p.right = p.parent = null;
}
//如果 p.red是真的不需要调整,黑色才balanceDeletion(root, replacement)
TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
//没有子节点的需要清除关系
if (replacement == p) { // detach
TreeNode<K,V> pp = p.parent; //获取p的父节点
p.parent = null;//清空p的父节点引用
if (pp != null) { //不是存在父节点
if (p == pp.left) //p是左元素,则清空pp的左元素
pp.left = null;
else if (p == pp.right)//p是右元素,则清空pp的右元素
pp.right = null;
}
}
if (movable)//remove 传进来的是false //不执行
moveRootToFront(tab, r);
}
//由树变了链表
//遍历树,遍历过程中 TreeNode->Node Node<K,V> p = map.replacementNode(q, null);
//设置节点的next 返回根节点
final Node<K,V> untreeify(HashMap<K,V> map) {
Node<K,V> hd = null, tl = null;
for (Node<K,V> q = this; q != null; q = q.next) {
Node<K,V> p = map.replacementNode(q, null); // TreeNode->Node
if (tl == null)
hd = p;
else
tl.next = p; //设置节点的next
tl = p;
}
return hd;// 返回根节点
}
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
return new Node<>(p.hash, p.key, p.value, next);
}
//红黑树4个场景
//逻辑
//第一步,就是判断x是不是空,又或者是不是等于根节点 是直接返回
//判断x的父节点xp ==null ,是那说明,x就是根节点 设置x为黑色 返回x
//判断x是不是红色,是红色就会设置为黑色,直接返回根节点,
//为什么呢,因为红色设置为黑色不就是弥补p的黑色吗,所以直接返回
//下面的代码主要是红黑色的删除调整情况
//情景1: 如果x的兄弟节点sib是个红色节点,那么设置sib为黑色,x的父节点p 为红色,然后以p为中心进行左旋
//情景2: 如果x的兄弟节点sib 的两个左右子节点都是黑色,那么设置sib为红色,x = x.parent
//情景3: 如果x的兄弟节点的右节点是个黑色,x的兄弟节点的左节点是个红色,
//那么设置sib为红色,x的兄弟节点的左节点为黑色,以sib为中心进行右旋
//情景4: 如果x的兄弟节点右节点是个红色,左节点任意颜色
//那么 x的父节点设置为黑色,x的兄弟节点的颜色设置为x的父节点的颜色,x的兄弟节点的右节点设置为黑色,以x的父节点为中心进行左旋
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
for (TreeNode<K,V> xp, xpl, xpr;;) {
//判断x是不是空,又或者是不是等于根节点
if (x == null || x == root)
return root;
//判断x的父节点xp ==null ,是那说明,x就是根节点 设置x为黑色 返回x
else if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//判断x是不是红色,是红色就会设置为黑色,直接返回根节点,
else if (x.red) {
x.red = false;
return root;
}
//判断x是左节点还是右节点
else if ((xpl = xp.left) == x) {
//判断x的兄弟节点是不是不是空,并且是个红色
//-----------情景1-------------
if ((xpr = xp.right) != null && xpr.red) {
//设置x的父节点为红色,x的兄弟节点为黑色,以x的父节点左旋
xpr.red = false;
xp.red = true;
root = rotateLeft(root, xp);
xpr = (xp = x.parent) == null ? null : xp.right;//获取新的兄弟节点
}
//如果没有兄弟节点,则想x = x的父节点
if (xpr == null)
x = xp;
//-----------情景1-------------
else {
//如果有兄弟节点
TreeNode<K,V> sl = xpr.left, sr = xpr.right;
//如果x的兄弟节点的左右节点都是黑色 或者不存在左右节点 设置x的兄弟节点为红色 ,x = x.parent
//-----------情景2-------------
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) {
xpr.red = true;
x = xp;
}
//-----------情景2-------------
else {
//如果x的兄弟节点的右节点是黑色,或者没有右节点
//-----------情景3-------------
if (sr == null || !sr.red) {
if (sl != null) //如果存在x的兄弟节点的左节点
sl.red = false; //设置x的兄弟节点的左节点为黑树
xpr.red = true;//设置x的兄弟节点sib为红色
root = rotateRight(root, xpr);//以x的兄弟节点sib为中心,进行右旋,sib左节点顺时针旋转,让sib左节点成为sib的父节点,sib为右节点
xpr = (xp = x.parent) == null ? //sib = 右旋后x的兄弟节点
null : xp.right;
}
//-----------情景3-------------
//-----------情景4-------------
if (xpr != null) {
xpr.red = (xp == null) ? false : xp.red; //将x父节点颜色 赋值给 x的兄弟节点
if ((sr = xpr.right) != null)//如果存在x的兄弟节点的右节点
sr.red = false;// 将x兄弟节点sib的右子节设为黑色
}
if (xp != null) {
xp.red = false;// 将x父节点设为黑色
root = rotateLeft(root, xp);;//对x的父节点进行左旋
}
//-----------情景4-------------
x = root;//设置x为根节点 调整完毕退出循环
}
}
}
else { // 下面的跟上面的是一个对称关系
if (xpl != null && xpl.red) {
xpl.red = false;
xp.red = true;
root = rotateRight(root, xp);
xpl = (xp = x.parent) == null ? null : xp.left;
}
if (xpl == null)
x = xp;
else {
TreeNode<K,V> sl = xpl.left, sr = xpl.right;
if ((sl == null || !sl.red) &&
(sr == null || !sr.red)) {
xpl.red = true;
x = xp;
}
else {
if (sl == null || !sl.red) {
if (sr != null)
sr.red = false;
xpl.red = true;
root = rotateLeft(root, xpl);
xpl = (xp = x.parent) == null ?
null : xp.left;
}
if (xpl != null) {
xpl.red = (xp == null) ? false : xp.red;
if ((sl = xpl.left) != null)
sl.red = false;
}
if (xp != null) {
xp.red = false;
root = rotateRight(root, xp);
}
x = root;
}
}
}
}
}
扩容 分析
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length; //记录旧的table.length
int oldThr = threshold;//记录旧的扩容因子
int newCap, newThr = 0;
//说明是已经初始化
if (oldCap > 0) {
//旧的已经是最大容量了 返回旧容量
//static final int MAXIMUM_CAPACITY = 1 << 30;
if (oldCap >= MAXIMUM_CAPACITY) { //如果table.length大于等于最大容量
threshold = Integer.MAX_VALUE;//threshold = Integer.MAX_VALUE
return oldTab; //返回旧的oldCap,没法再大了
}
//newCap = oldCap << 1
//newThr = oldThr << 1
//static final int MAXIMUM_CAPACITY = 1 << 30 = 1073741824 ;
//static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 = 16; 默认的初始容量-必须是2的幂。
//oldCap << 1 翻一倍 1 << 4 = 16 <= newCap = 旧的长度 * 2 < 1 << 30
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold //设置为触发下一次调整大小的值 newThr = oldThr * 2
}
//new HashMap(initialCapacity) 会设置this.threshold
else if (oldThr > 0) // initial capacity was placed in threshold 初始容量设置为threshold
newCap = oldThr; //触发下一次调整大小的值
//new HashMap()
else { // zero initial threshold signifies using defaults 零初始阈值表示使用默认值
newCap = DEFAULT_INITIAL_CAPACITY; //使用默认值
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//触发下一次调整大小的值
}
//计算 newThr
if (newThr == 0) {
float ft = (float)newCap * loadFactor;//ft = 触发下一次调整大小的值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
//当符合条件时newThr = ft ,不符合条件 Integer.MAX_VALUE
}
threshold = newThr; //threshold = newThr
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //创建一个新的数组
table = newTab; // table = newTab
if (oldTab != null) { //oldTab != null
for (int j = 0; j < oldCap; ++j) { //遍历旧数组
Node<K,V> e;
if ((e = oldTab[j]) != null) { //元素不是空 e = oldTab[j]
oldTab[j] = null;//把oldTab[j] =null
if (e.next == null) //e没有下一个节点
//直接在新的数组里面根据e.hash & (newCap - 1) 计算新的位置存放
newTab[e.hash & (newCap - 1)] = e;
//判断e是否是个树节点
else if (e instanceof TreeNode)
//进行树节点的拷贝
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
//这里应该是链表处理
//
Node<K,V> loHead = null, loTail = null;
//
Node<K,V> hiHead = null, hiTail = null;
//
Node<K,V> next;
do {
next = e.next;
/**
* 测试目的:理解HashMap发生resize扩容的时候对于链表的优化处理:
* 初始化一个长度为8的HashMap,因此threshold为6,所以当添加第7个数据的时候会发生扩容;
* Map的Key为Integer,因为整数型的hash等于自身;
* 由于hashMap是根据hash &(n - 1)来确定key所在的数组下标位置的,因此根据公式 m(m >= 1)* capacity + hash碰撞的数组索引下标index,可以拿到一组发生hash碰撞的数据;
* 例如本例子capacity = 8, index = 7,数据为:15,23,31,39,47,55,63;
* 有兴趣的读者,可以自己动手过后选择一组不同的数据样本进行测试。
* 根据hash &(n - 1), n = 8 二进制1000 扩容后 n = 16 二进制10000, 当8的时候由后3位决定位置,16由后4位。
*
* n - 1 : 0111 & index resize--> 1111 & index
* 15 : 1111 = 0111 resize--> 1111 = 1111
* 23 : 10111 = 0111 resize--> 10111 = 0111
* 31 : 11111 = 0111 resize--> 11111 = 1111
* 39 : 100111 = 0111 resize--> 100111 = 0111
* 47 : 101111 = 0111 resize--> 101111 = 1111
* 55 : 110111 = 0111 resize--> 110111 = 0111
* 63 : 111111 = 0111 resize--> 111111 = 1111
*
* 按照传统的方式扩容的话那么需要去遍历链表,然后跟put的时候一样对比key,==,equals,最后再放入新的索引位置;
* 但是从上面数据可以发现原先所有的数据都落在了7的位置上,当发生扩容时候只有15,31,47,63需要移动(index发生了变化),其他的不需要移动;
* 那么如何区分哪些需要移动,哪些不需要移动呢?
* 通过key的hash值直接对old capacity进行按位与&操作如果结果等于0,那么不需要移动反之需要进行移动并且移动的位置等于old capacity + 当前index。
*
* hash & old capacity(8)
* n : 1000 & index
* 15 : 1111 = 1000
* 23 : 10111 = 0000
* 31 : 11111 = 1000
* 39 : 100111 = 0000
* 47 : 101111 = 1000
* 55 : 110111 = 0000
* 63 : 111111 = 1000
*
* 从下面截图可以看到通过源码中的处理方式可以拿到两个链表,需要移动的链表15->31->47->63,不需要移动的链表23->39->55;
* 因此扩容的时候只需要把loHead放到原来的下标索引j(本例j=7),hiHead放到oldCap + j(本例为8 + 7 = 15)
*
* @param args
————————————————
版权声明:本文为CSDN博主「青元子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u010890358/article/details/80496144
*/
//这里分析得真好,谢谢大佬
//我这里总结一下
//e.hash & (newCap - 1) 不管你e.hash是什么值 ,在计算下标的时候,控制你在那个地方的是(newCap-1)
//而newCap 是个2的n次方,这是个很特别的数,这个数减1 二进制必定是长这样子的 111(8-1),1111(16-1),11111(32-1),11111(64-1)等
//比如8 二进制是 111,那么下面举例子说明
// e.hash 的二进制 = 01001010111010101011011 我不知十进制是什么值,但无所谓,(newCap - 1) 二进制是 111
//01001010111010101011011
// &
//00000000000000000000111
// =
//00000000000000000000011
//结果下标就是 11 十进制是3,你看,不管你的e.hash是什么,下标必定在111的范围内,所以e.hash真正参加计算的只是后三位而然
//扩容
//当容量不够进行扩容时,把数组的长度扩大到 oldCap << 1; 记得oldCap是个2的n次方 这里举例oldCap=8
//00000000000000000001000
// << 1 (意思是位向左移动一位,右边补0)
//00000000000000000010000
//最后结果转十进制等于16 上面我们数组存了一个元素01001010111010101011011 那他怎么处理呢,还是一样的处理
//01001010111010101011011
// &
//00000000000000000001111 (16-1)
//00000000000000000001011
//结果下标就是 1011 十进制是11,你看,不管你的e.hash是什么,下标必定在1111的范围内,所以e.hash真正参加计算的只是后四位而然
//举例 oldCap=8 newCap=16
//a元素的分析 a元素的hashcode的二进制 = 01001010111010101011011
//011(元素二进制的后三位) & 111(oldCap-1) = 0011 未扩容时
//1011(元素二进制的后四位) & 1111(newCap-1) = 1011 扩容后
//b元素的分析 b元素的hashcode的二进制 = 01001010111010101010011
//011(元素二进制的后三位) & 111(oldCap-1) = 0011 未扩容时
//0011(元素二进制的后四位) & 1111(newCap-1) = 0011 扩容后
//根据上面的数据进行如下分析
//第一个结论,(oldCap-1)的二进制比(newCap-1)的二进制 少一位1
//第二个结论,在进行下标计算时 ,元素的hashcode的二进制真正有用到的位置
//未扩容,后三位
//扩容后,后四位
//第三个结论,计算出来的下标
//a元素 未扩容的下边跟扩容后的下边相差一个1000
//b元素 未扩容的下边跟扩容后的下边相等
//仔细观察a,b元素的二级制
//a 01001010111010101011011
//b 01001010111010101010011
//你会惊讶的发现,a和b只是倒数第四位不一样,a 是1,b是0
//然而 计算出来的下标结论是
//a元素 未扩容的下边跟扩容后的下边相差一个1000
//b元素 未扩容的下边跟扩容后的下边相等,也即是位置不会发生改变
//这里已经说明,第四位是0和1,对于计算下标有着天大的影响
//那现在来分析(e.hash & oldCap) oldCap = 8
//a元素 1011(元素二进制的后四位 & 1000 = 1000
//b元素 0011(元素二进制的后四位 & 1000 = 0000
//先提出一个问题为什么是& oldCap 不是其他呢
//oldCap的二级制是多少 1000 发现了什么吗 它的倒数第4位是个1
//根据上面的分析,第四位是0和1,对于计算下标有着天大的影响
//元素的第四个位置是0,那么位置不变
//元素的第四个位置是1,那么位置改变
//而oldCap是一个2的n次方的数,2的n次方的数是很有规律的,10(2) (100)4 (1000)8 (10000)16 等
//0111 (oldCap-1) 未扩容后
//1111 (newCap-1) 扩容后
//1000 oldCap 未扩容后
//10000 newCap 扩容后
//因为1000的第四位是1 而且1000后三位是0,在参与&运算时,只会比较第四位,其他位都是0
//所以(e.hash & oldCap) == 0 就知道 e.hash的第四位是0,否则就是1
//所以明白了为什是oldCap了吗
//至于为什么新的位置是旧的位置是旧的位置+旧的长度
//第三个结论 如果第四个位置是1,未扩容的下边跟扩容后的下边相差一个1000,这个1000熟悉不,不就是oldCap吗?
if ((e.hash & oldCap) == 0) {
//第一条链表
// 第一次
// loTail
// loHead
// 1
// 第二次
// loTail
// loHead
// 1 -> 2
//第三次
// loTail
// loHead
// 1 -> 2 -> 3
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; //第一条链表所在地方
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead; //第二条链表的位置是 旧的长度位置+旧的位置j
}
}
}
}
}
return newTab;
}
//((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
for (TreeNode<K,V> e = b, next; e != null; e = next) { //遍历这棵树
next = (TreeNode<K,V>)e.next;
e.next = null;
//判断e.hash是否需要移动位置
// 第一次
// loTail
// loHead
// 1
// 第二次
// loTail
// loHead
// 1 -> 2
//第三次
// loTail
// loHead
// 1 -> 2 -> 3
if ((e.hash & bit) == 0) { //不需要移动的
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
else {//同上
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
//static final int UNTREEIFY_THRESHOLD = 6
//调整大小的时候,如果树的节点数少于6,会进行树的退化
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;//设置根元素
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
if (hiHead != null) { //同理
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead; //新位置就是旧位置加上旧长度,跟链表一样,已在上面分析
if (loHead != null)
hiHead.treeify(tab);
}
}
}
//由树变了链表
//遍历树,遍历过程中 TreeNode->Node Node<K,V> p = map.replacementNode(q, null);
//设置节点的next 返回根节点
final Node<K,V> untreeify(HashMap<K,V> map) {
Node<K,V> hd = null, tl = null;
for (Node<K,V> q = this; q != null; q = q.next) {
Node<K,V> p = map.replacementNode(q, null); // TreeNode->Node
if (tl == null)
hd = p;
else
tl.next = p; //设置节点的next
tl = p;
}
return hd;// 返回根节点
}
//tab =hashmap存放元素的array
//this = hd 头部元素,现在只是一个双向链表
//这里的逻辑是遍历链表
//如果没有根元素,那么设置根元素 root == null x.parent = null; x.red = false; root = x;
//如果存在根节点,那么就遍历树,从根元素开始
//先根据节点的hash值进行比较
//如果hash值一样,比较key值是否相等
///如果hash值一样,根据key的comparableTo比较大小,
//如果key没有没有实现comparable,最后使用System.identityHashCode()进行比较,
//System.identityHashCode(a) 无论给定对象的类是否覆盖hashCode(),
//返回给定对象与默认方法hashCode()返回的相同哈希代码。空引用的哈希码为零
//最后根据比较的值 绑定关系 dir <= 0 ? p.left : p.right
// TreeNode<K,V> xp = p; x.parent = xp; dir <= 0 ? xp.left = x :xp.right = x;
//绑定关系后就需要保证红黑树没有违反规则,则调用balanceInsertion
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
//x = hd
//next = x.next
//遍历的是列表
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
//左右节点都是null
x.left = x.right = null;
//如果不存在根节点
if (root == null) {
x.parent = null; //x的parent设置为空
x.red = false; //红黑树的根节点是黑色。
root = x; //设置x 为根节点
}
else { //如果存在根节点,也即是不是根节点怎么放
K k = x.key; //取出需要处理的节点x的key
int h = x.hash;///取出需要处理的节点x的hash
Class<?> kc = null; //
//死循环
//TreeNode<K,V> p = root
//遍历的是树呢
for (TreeNode<K,V> p = root;;) {
int dir; //记录p节点和x节点的hash值比较状态 大于-1 小于1
int ph; //节点hsah值
K pk = p.key; //取出节点的key
if ((ph = p.hash) > h) //p节点的hash值> x节点的hash值
dir = -1; //大于设置状态-1
else if (ph < h) //p节点的hash值 < x节点的hash值
dir = 1; //小于设置状态-1
//这里就是等于的意思了
//kc = comparableClassFor(k) ==>x的class
//dir = compareComparables(kc, k, pk) ==>x,p的比较值
//kc = null,
//comparableClassFor(k),获取x的key的类型,
//如果是实现了comparable的就返回x的key的类型,否则返回null,见下面函数分析
//compareComparables,通过 comparable 获取比较的值,没有实现comparable,
//或者p的key的class和x的class 不一样,返回0,见下面函数分析
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
//使用默认的hashcode进行相减,见下面函数分析
dir = tieBreakOrder(k, pk);
//xp = 节点p
TreeNode<K,V> xp = p;
//p > x = -1
//p = 大于 p.left 小于p.right
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//记得xp等于之前的p 进来if p= p.left or p.right 设置x的爸爸是xp
x.parent = xp;
if (dir <= 0) // p > x = -1
xp.left = x; //大于是左边
else
xp.right = x;//小于是右边
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
右旋

左旋

网友评论