什么是哈希和散列表?
百度百科
简单的理解就是 把A -> 算法->B, 即把A通过某种算法变成B的过程,在java中,hashCode作为Object的一个本地成员方法,他提供的是对象在内存地址中的一个映射,是不会重复的,也就是说不会造成hash冲突, 所以判断两个对象是否相等如果没有重写hashCode,只要hashCode相等了那么这两个对象也一定相等。在java中判断对象是否相等可以使用equals,一般来说如果两个对象相等,hashCode也是一定相等的,所以在重写equals方法的时候,hashCode也要进行重写。保证整个原则。
为什么要用HashMap
hashMap到底解决了什么问题????? 或者说散列函数解决了什么问题???
说到存储对象: 数组和链表是我们常用的,,比如有一个学生对象。用来使用数组或者链表用来存储其实查找效率都差不多, 效率查找差不多的前提是 学生对象我不知道在数组里面的那个下标,所以只能从头到尾进行遍历和链表一样。但是我们知道数组存储空间上是连续的,如果知道下标就可以立马定位出对象所在的地址,而链表则不同,因为链表节点和节点之间是没有规律的间隔,他们的地址都是随机在内存中分配,所以即使知道对象在第几个节点也没用,不能像数组一样立马计算出来地址,还是只能遍历进行查找,所以数组如果能确定对象的下标,查找效率是飞快的。 那么怎么来确定下标呢?一般可以用过取模的方式或者按位&, 比如一个对象的hashCode是100, 那么对于20个长度的数组下标他在数组中的位置就是100%20 = 0, 即 下标为0的位置, 所以如果对象的存储的时候都是放入自己hashCode与长度取模或者&的位置,那么我们要查找一个对象是不是快的飞起。那问题来了,我都知道对象的hashCode了,那说明我已经拥有了对象,那何必还要查找呢? 哈哈哈,所以问题就来了, 所以如果使用对象hashCode进行数组的存储时不对的。对于一个学生对象来说,每个学生对象的学号时唯一的,学号时可以代表学生的。所以我们可以使用学号作为学生对象在数组中的位置, 比如学号为1000, 在长度为20的数组中的位置为 0 , 这样只要提供了学号,不就能立即获取学生对象,这个时间复杂度可以说是O(1)了,不要太快了,那问题来了,20个数组长度我们最多放20个学生,而且学号与20取模之后不能相等,必须保证每个位置只有一个,所以问题来了。 没事那我数组长度定大一点, 超过学生学号长度就行。这个确实可以,但是随之而来的问题就是我在追求机制查询速度的同时,数组容量也在扩大。容量扩大之后带来的问题就是原先的hash不能用了,需要重新迁移。扩大容量也就是解决hash冲突的一种方法,容量扩大之后进行重新hash,但是很浪费空间,如果数据不规律。就会导致大量空间被浪费,所以解决hash冲突的另外一种方法就是链地址法,如果同一个hash位置上冲突了,那么就把这个对象挂在后面。但是这样也会导致一个问题,链表太长了,导致效率查询变成了O(n), 所以链表长度到达一定量就可以转化为红黑树(为啥不是二叉排序树或者AVL树,因为这两个树相比红黑树来说,性能不够好,二叉排序树有极端的情况存在退化成链表,AVL树则是过于追求平衡)。
那问题又来了,如果存储对象越来越多,冲突越来越严重,就会导致红黑树过高,所以我们还是配合对table进行扩容(使用扩大容量来适当的减少hash冲突)。 使用K V的结构来代表关系。
呃呃呃, 说不下去了,自己太菜了,不能表示为啥需要K V的这种结构, 呃, TreeMap和HashMap的区别本质区别有人想过吗。。。。一个能排序和一个不能排序这个是现在,但是本质是什么,为什么会造成这种结果? 因为 hashMap是散列函数+散列表+链表或者红黑树,散列函数的是不具备排序性的,它找的一是一种散列平衡,映射算法, 而TreeMap则是使用Key做为排序条件的纯纯的红黑树的结构,红黑树就是二叉排序树,所以具有排序的特点。。。。。
hashMap的所有节点类型:
整体的节点类型如上图所示。。。
Entry 即是为了保持put时候的先后顺序。 也就是LinkedhashMap有序的原因
TreeNode的Prev用来干嘛?不是已经有了parent或者before???
hashMap的构造函数
默认table表的容量为16, 加载因子为0.75
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) //小于0就抛出异常
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) //大于2的30次方 最大容量
initialCapacity = MAXIMUM_CAPACITY; //设置最大容量
if (loadFactor <= 0 || Float.isNaN(loadFactor)) //小于0 或者不是也给数字
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor; //设置加载因子
this.threshold = tableSizeFor(initialCapacity); //获取initialCapacity的2幂次的最小值
}
hashMap的构造函数有三个,都是没有对table表进行初始化,所以hashMap是懒加载的。
tableSizeFor
这个方法真的很牛逼, 获取 二次幂,这个二次幂是传入>= 入参int整数的最小值。比如 传入4, 那就返回 4, 传入 6, 就返回8, 这个方法的算法牛逼。
如何获取? , 把一个数的为1的最高位到最地位都变成1,然后加1就是变成了二进制2的幂次。
static final int tableSizeFor(int cap) { //这个算法是为了获取设置容量2幂次方的最小值 hashMap的容量为啥 必须是2的幂次方 因为这样的数减1之后都是111111,这样方便做hash
int n = cap - 1; //这里为啥要-1, 因为如果如果cap是偶数,比如4,如果不减少1, n会加1,返回值就是8
n |= n >>> 1; //最高位1和最高位的第二位为 1
n |= n >>> 2; //最高位到后面四位为1
n |= n >>> 4; //最高位到后面8位为1
n |= n >>> 8; //最高位到后面16为1
n |= n >>> 16; //最高位到后面32位 为1
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; //负数就放回1,这里+1就可以进一位无敌啊。
}
hashMap的put方法
public V put(K key, V value) { //添加一个k - v
return putVal(hash(key), key, value, false, true);//hash()获取hash
}
hash
这个方法就是获取hash 为了散列均匀。。,不直接使用key的hashCode,
static final int hash(Object key) { //高16异或低16位 hash均匀,避免hash冲突, 提交效率
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //异或 无符号右移
}
hashMap是可以存储key为Null的值, key为null会被分配到数组的0号下标hash槽位上
putVal
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) //第一次添加,需要进行hash表的初始化
n = (tab = resize()).length; //数组容量分配
if ((p = tab[i = (n - 1) & hash]) == null) //获取槽位 这个采用&的方式,而不是取模,效率高,注意这里 n代表容量,n是2的次幂,所以n-1之后的二进制 都是111..., 这样 直接hash 得到的与值是在0~ n-1 的范围
tab[i] = newNode(hash, key, value, null); //没有元素直接赋值 创建Node类型的节点
else { //发生hash冲突, 使用链表地址法解决冲突
Node<K,V> e; K k; //p是指向hash位置的node节点
if (p.hash == hash && //对象相等hashCode相等(前提是hashCode没有被自己重写),那么经过hash映射后的得到的hash值一定相等,但是要考虑hash冲突,即hashCode不相等但是经过hash函数之后得到的hash相等
((k = p.key) == key || (key != null && key.equals(k)))) //hash相同 && (地址相同 || equals)
e = p; //相等
else if (p instanceof TreeNode) //是否树化
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {//进行链表遍历,如果binCount大于树化阈值-1 进行尝试树化
if ((e = p.next) == null) { //获取下一节点 这里的p指向hash槽位的节点
p.next = newNode(hash, key, value, null); //把一个新的节点接上
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st binCount >= 7进行树化
treeifyBin(tab, hash); //树化 这里的长度判断是从hash点的next开始计算,不包括hash表里面的node节点
break; //找到就跳出
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) //如果有匹配的节点
break;
p = e;
}
}
if (e != null) { // existing mapping for key e不为null说明 是替换
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) //onlyIfAbsent 没有才会替换 或者 oldValue为null 也进行替换
e.value = value; //替换
afterNodeAccess(e); //给LinkedHashMap 扩展的方法 模板方法
return oldValue; //如果进行替换 返回旧的值 modCount 不做加1
}
}
++modCount; //添加次数加一, 这里是为了 迭代器的时候 的并发修改异常 做为判断标志。
if (++size > threshold) //这里的size是所有的HashMap的节点个数,不是Hash数组里面的节点个数
resize(); //重新hash 设计
afterNodeInsertion(evict); //节点后插入,这里给LinkedHashMap使用 模板方法
return null;
}
链表长度为8就会尝试进扩容, 提供模板方法,给子类使用, key可以为null, 放在0号槽位(或者0号槽位的链表上)
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) //如果hash表(数组的长度还没有到 64 默认值)
resize(); //只进行 hash数组重新扩容 散列
else if ((e = tab[index = (n - 1) & hash]) != null) { //根据hash获取table数组里面的下标得到要树化的node, e指向了槽位的第一个节点
TreeNode<K,V> hd = null, tl = null; //hd指向TreeNode 双向链表的头
do {
TreeNode<K,V> p = replacementTreeNode(e, null);//把Node节点转换为TreeNode节点, 注意啊 这里的next是为null的说明红黑树的节点next为null
if (tl == null)
hd = p; //hd指向头节点 头节点的pre为空
else {
p.prev = tl; //指向前的节点,
tl.next = p; //指向后一个节点
}
tl = p;
} while ((e = e.next) != null);//把之前的单向Node链表变为TreeNode双向链表
if ((tab[index] = hd) != null) //把头节点 放入hash表中
hd.treeify(tab); //hd为头节点 进行树化
}
}
可见 TreeNode的 prev和next 保持了 原先链表的顺序
resize
final Node<K,V>[] resize() { //重置hash表
Node<K,V>[] oldTab = table; //记录原table表 数组 引用
int oldCap = (oldTab == null) ? 0 : oldTab.length; //原table表的容量
int oldThr = threshold; //记录原阈值
int newCap, newThr = 0;
if (oldCap > 0) { //原容量大于0
if (oldCap >= MAXIMUM_CAPACITY) { //极端情况
threshold = Integer.MAX_VALUE; //设置最大的阈值
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && //新的容量为原容量的2倍
oldCap >= DEFAULT_INITIAL_CAPACITY) //原容量大于等于初始容量 16
newThr = oldThr << 1; // double threshold 这里 的阈值*2 如果 久的hash数组的长度没有到达16 是不会把数组扩容阈值进行调高
} //原容量为0
else if (oldThr > 0) // initial capacity was placed in threshold 原阈值大于0
newCap = oldThr; //新容量为原阈值
else { // zero initial threshold signifies using defaults 原阈值为0
newCap = DEFAULT_INITIAL_CAPACITY; //默认的容量 16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //加载因子*16 = 12
}
if (newThr == 0) { //阈值没有设置
float ft = (float)newCap * loadFactor; //新的容量 * 加载因子
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE); //如果新容量或者阈值超过最大容量 设置为int类型的最大值
}
threshold = newThr; //新扩容的阈值
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//建立hash表
table = newTab; //进行引用指向
if (oldTab != null) { //原table表不为null ,进行迁移
for (int j = 0; j < oldCap; ++j) { //遍历table表
Node<K,V> e;
if ((e = oldTab[j]) != null) { //如果该hash数组下标位置有节点
oldTab[j] = null; //放弃对节点的引用
if (e.next == null) //说明只有一个节点
newTab[e.hash & (newCap - 1)] = e; //计算下标直接赋值
else if (e instanceof TreeNode) //改槽位的节点已经变成树节点
((TreeNode<K,V>)e).split(this, newTab, j, oldCap); //this 指的是hashmap 对象, newTabl新的hash表, j原来hash表的位置, oldCap原来老的容量
else { // preserve order 链表 记住:表长度扩大为2被, 那么链上的节点的位置要么不变, 要么变成原下标位置+原容量
Node<K,V> loHead = null, loTail = null; //比如 hash 为 2 和 12 数组原容量为 10, 扩容之后新容量为20, 那么hash为2的还是在原先位置,hash为12的位置变为 原先位置加旧容量为12,或者重新hash也行
Node<K,V> hiHead = null, hiTail = null; //lo 记录原位置, hi记录去高位置
Node<K,V> next;
do {
next = e.next; //next指向e.next节点
if ((e.hash & oldCap) == 0) { //原来的hash与老的容量进行与运算 说明 高位 是零, 那么扩容之后hash位置不变
if (loTail == null)
loHead = e; // loTail 记录头 节点
else
loTail.next = e;
loTail = e;
}
else { ///说明 hash的地16位不为0, 容量变为 原来的下标 + old容量
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null); //e指向自己的next节点
if (loTail != null) { //尾节点赋值为null 注:原链表的末尾不知道是跑到高位还是继续在低位上,所以都要进行判断。
loTail.next = null;
newTab[j] = loHead; //指向
}
if (hiTail != null) { //尾节点赋值为null
hiTail.next = null;
newTab[j + oldCap] = hiHead; //指向
}
}
}
}
}
return newTab;
}
split
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { //同样的红黑树也面临这重新hash之后下标改变的两种情况
TreeNode<K,V> b = this; //记录自身
// Relink into lo and hi lists, preserving order
TreeNode<K,V> loHead = null, loTail = null; //0 重新hash不变
TreeNode<K,V> hiHead = null, hiTail = null; //1 重新hash变
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; //注意这里 把 next置为空了,所以分割之后这两条桶互不相干的--从双向链表的角度
if ((e.hash & bit) == 0) { //重新hash不变
if ((e.prev = loTail) == null) //尾巴节点位null
loHead = e; //头节点
else
loTail.next = e;
loTail = e;
++lc; //数量+1
}
else { //hash到高位了
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
//这里分为好几种情况: 1、hash之后 原来的双向链表都还在 原来的下标位置,所以红黑树结构不需要改变; 2、如果都跑到原来下标+旧容量的位置,红黑树结构也不需要进行改变; 3、如果原双向链表已经被分开了需要进行重新树化 4、反树化
if (loHead != null) { //右节点被hash到高位
if (lc <= UNTREEIFY_THRESHOLD) //是否需要反树化 数量小于等于6 就进行反树化
tab[index] = loHead.untreeify(map);
else { //不需要进行反树化
tab[index] = loHead; //插入table表 低位
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);
}
}
}
整体逻辑就是原位置和新位置的分割, 对于红黑树节点需要注意,分割之后需要个自己形成自己的红黑树或者进行反树化。
treeify
final void treeify(Node<K,V>[] tab) { //tab hash数组 注意:在树化的过程中 prev和next是保持不变的
TreeNode<K,V> root = null; //root始终指向根节点
for (TreeNode<K,V> x = this, next; x != null; x = next) { //遍历整个链表以this为起点的链表
next = (TreeNode<K,V>)x.next; //next指向下一个
x.left = x.right = null;
if (root == null) { //根节点
x.parent = null;
x.red = false; //根节点黑色
root = x; //root 指向根节点
}
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) //向右
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; //记录当前p的指向
if ((p = (dir <= 0) ? p.left : p.right) == null) { //如果为null,说明已经到最底层了
x.parent = xp;
if (dir <= 0) //判断是挂在左边还是右边
xp.left = x;
else
xp.right = x;
root = balanceInsertion(root, x); //插入之后要进行黑色平衡
break;
}
}
}
}
moveRootToFront(tab, root); //把根节点移动到table表的hash位置
}
moveRootToFront
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;
if (root != null && tab != null && (n = tab.length) > 0) {//如果根节点不为null table不为null 切长度大于0
int index = (n - 1) & root.hash; //获取root节点在table中的位置下标
TreeNode<K,V> first = (TreeNode<K,V>)tab[index]; //获取当前为值的TreeNode, 也就是双向链表的头节点
if (root != first) { //双向链表的头节点不是红黑树的根节点
Node<K,V> rn;
tab[index] = root; //指向 红黑树的根节点
TreeNode<K,V> rp = root.prev; //得到根节点 之前的双向树节点链表中的前驱节点
if ((rn = root.next) != null) //此时的根节点 不是 双向链表中的最后一个
((TreeNode<K,V>)rn).prev = rp;
if (rp != null) //把first变成root的后继,把root变成双向链表对头指针
rp.next = rn;
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
assert checkInvariants(root); //检查红黑树的性质
}
}
untreeify
**反树化,而其实思路很简单。 不要想着真的就是去吧红黑树反树化, 我们知道树节点也是保持双向链表结构的,所以反树化,只要把这个TreeNode的双向链表结构变成Node的单向链表结构就行了, 原来的红黑没有根对象引用,就会被GC回收 **
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) { //this 就是双向链表的根节点,
Node<K,V> p = map.replacementNode(q, null); //把treeNode转化成node
if (tl == null)
hd = p; //hd指向头节点
else
tl.next = p;
tl = p;
}
return hd;
}
getNode
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) { //如果table初始化了,切hash得到的下标里的引用不为null
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k)))) //对hash槽位的第一个节点判断是否相等
return first;
if ((e = first.next) != null) { //说明有hash冲突
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;
}
getTreeNode
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null); //找到红黑树的根节点然后开始找
}
root
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) { //一直找自己的父亲
if ((p = r.parent) == null)
return r;
r = p;
}
}
find
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))) //当前节点 hash和h一样,那么判断是否相等
return p;
else if (pl == null) //如果左子树为空, 去右子树
p = pr;
else if (pr == null) //如果右子树为空,去左子树
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0) //使用比较器进行比较
p = (dir < 0) ? pl : pr;
else if ((q = pr.find(h, k, kc)) != null) //左右子树都不为空,比较其又比较不出走那一边, 直接向走右子树
return q;
else //右子树找了一圈还是找不到, 走左子树
p = pl;
} while (p != null);
return null;
}
putTreeVal
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, ph; K pk;
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;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) { //不相等,使用比较器确定走左边还是右边 如果比较器比较不出来
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;
}
dir = tieBreakOrder(k, pk); //使用类的名字或者hashCode进行比较
}
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) { //是否已经到了末尾
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.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
moveRootToFront(tab, balanceInsertion(root, x)); //树平衡
return null;
}
}
}
removeNode
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, 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;
else if ((e = p.next) != null) { //hash冲突
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;
} while ((e = e.next) != null);
}
}
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;//p节点没人指向, 所以p节点被GC
else //链表上的节点
p.next = node.next;
++modCount; //修改次数加一
--size; //节点数量减1
afterNodeRemoval(node); //留给子类扩展的模板方法
return node;
}
}
return null;
}
removeTreeNode 红黑树节点的删除
删除的节点是不是黑色,需要保持平衡, 是否导致反树化条件, root节点是否改变。
HashMap的删除操作和TreeMa这种经典红黑树的删除操作更复杂,因为HashMap维护了TreeNode的双向链表结构,对于删除节点左右孩子都不为Null的情况下,是不能替换成删除后继或者前驱
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
int n;
if (tab == null || (n = tab.length) == 0) //table表没有初始化
return;
int index = (n - 1) & hash; //获取下标
TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev; //树节点双向链表中的后继和前驱
if (pred == null) //说明删除节点属于双向链表中的头节点(其实也就是红黑树根节点)
tab[index] = first = succ; //红黑树的双向链表的头节点换成后继
else //删除的不是双向链表的头节点
pred.next = succ; //跨过删除的节点
if (succ != null)
succ.prev = pred; //指向被删除节点在双向链表中的后继节点的前驱指针指向删除节点的前驱节点
if (first == null) //说明红黑树就一个根节点删除就删除了
return;
if (root.parent != null) //这种情况啥时候出现? 绝了 不知道
root = root.root(); //找到根节点
if (root == null
|| (movable
&& (root.right == null //最多2个节点
|| (rl = root.left) == null //最多2个节点
|| rl.left == null))) { //这个条件比较难懂
tab[index] = first.untreeify(map); // too small 反树化, 因为双向链表已经对this节点进行了删除,反树化之后节点也是不存在的
return;
}/*以上的操作为双向链表的操作, 接着要进行红黑树的删除 注意:这里的删除操作和TreeMap的经典红黑树删除操作是不一样的,因为有双向链表的存在,所以对于删除节点的左右子树都不为null的节点是不能先Treemap一样直接替换成去删除后继或者前驱*/
TreeNode<K,V> p = this, pl = left, pr = right, replacement; //p为红黑树中要除去的节点,
if (pl != null && pr != null) { //如果删除节点 的左右孩子都不为空
TreeNode<K,V> s = pr, sl; //s 为p的后继
while ((sl = s.left) != null) // find successor 找到中序遍历删除节点的后继节点
s = sl;
boolean c = s.red; s.red = p.red; p.red = c; // swap colors 与后继节点 进行颜色交换
TreeNode<K,V> sr = s.right;
TreeNode<K,V> pp = p.parent;
if (s == pr) { // p was s's direct parent p是s(后继节点的直接父母)
p.parent = s; //后继节点作为 p的父亲节点
s.right = p; // 准备把p和s进行置为调换
}
else { //p不是后继节点的直接父亲
TreeNode<K,V> sp = s.parent;
if ((p.parent = sp) != null) {
if (s == sp.left) //如果s是父亲的左孩子
sp.left = p;
else //右孩子
sp.right = p;
}
if ((s.right = pr) != null)
pr.parent = s;
}
p.left = null; //p.left置为null
if ((p.right = sr) != null) //p变成了原先s的位置,那么原先s的右边孩子,需要接在p的右边
sr.parent = p; //sr的父亲是p
if ((s.left = pl) != null) //替换之后 s.left接替 之前p的left
pl.parent = s; //pl的父亲现在成为了 s
if ((s.parent = pp) == null) //s节点的父亲要是p节点的父亲
root = s; //说明替换之前 p节点就是根节点
else if (p == pp.left) //判断p是其父亲节点的左孩子 还是 右孩子
pp.left = s;
else
pp.right = s;
if (sr != null)
replacement = sr; //sr要来代替p
else
replacement = p; //说明 s是叶子节点, 直接删除p就行了,然后看看颜色要不要变
}
else if (pl != null) //删除节点的右孩子为空
replacement = pl;
else if (pr != null) //删除节点的左孩子为null
replacement = pr;
else //左右孩子都为null
replacement = p; //说明删除的节点 是叶子节点
if (replacement != p) { //即删除节点的孩子节点存在
TreeNode<K,V> pp = replacement.parent = p.parent; //代替节点的父亲 指向 被代替节点的父亲
if (pp == null) //说明p就是根节点
root = replacement;
else if (p == pp.left)
pp.left = replacement;
else
pp.right = replacement;
p.left = p.right = p.parent = null; //GC p节点从红黑树中删除
}
TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement); //删除节点的颜色是否是黑色,如果是黑色需要进行黑色平衡调整
if (replacement == p) { // detach 说明删除节点的孩子节点左右都为null
TreeNode<K,V> pp = p.parent;
p.parent = null;
if (pp != null) { //删除节点
if (p == pp.left)
pp.left = null;
else if (p == pp.right)
pp.right = null;
}
}
if (movable)
moveRootToFront(tab, r); //把红黑树的头节点移动到槽位
}
balanceDeletion
// 删除后颜色平衡调整
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
for (TreeNode<K,V> xp, xpl, xpr;;) {
if (x == null || x == root) //调整节点为null或者调整节点就是根节点
return root;
else if ((xp = x.parent) == null) { //如果替代节点的父亲节点为null,说明x替代成为了root
x.red = false; //root节点的颜色为黑色
return x;
}
else if (x.red) { //调整节点的颜色为红色,直接变成黑色补充就行
x.red = false;
return root;
}
else if ((xpl = xp.left) == x) {//如果x是父亲节点的左孩子
if ((xpr = xp.right) != null && xpr.red) { //如果父亲节点的右孩子不为null并且为红色 说明xp的颜色为黑色。 进行自损
xpr.red = false; //父亲节点的右孩子变成黑色
xp.red = true; //父亲节点变成红色
root = rotateLeft(root, xp); //左旋
xpr = (xp = x.parent) == null ? null : xp.right; //重新指向新的关系
}
if (xpr == null) //x的右孩子为null
x = xp; //自损到上一个节点
else {//说明xp的右孩子节点是黑色
TreeNode<K,V> sl = xpr.left, sr = xpr.right;
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) { //判断是不是能直接进行变红
xpr.red = true;//变红 自损
x = xp; //新的平衡点
}
else {
if (sr == null || !sr.red) { //如果右子树为null或者是黑色,
if (sl != null)
sl.red = false;
xpr.red = true; //设置为红色
root = rotateRight(root, xpr); //进行右旋 旋转成一条线
xpr = (xp = x.parent) == null ?
null : xp.right; //重新确定关系
}
if (xpr != null) {
xpr.red = (xp == null) ? false : xp.red; //与xp节点的颜色保持一直
if ((sr = xpr.right) != null)
sr.red = false; //设置为黑色
}
if (xp != null) {
xp.red = false;
root = rotateLeft(root, xp);//左旋。
}
x = root;
}
}
}
else { // symmetric
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;
}
}
}
}