前面三篇都是给本篇作辅助用的,现在正式来了解一下HashMap这个集合容器,首先拿出集合接口图谱:
由上面的图谱可知HashMap是属于Map接口下的,和Set与List接口属于两个体系,再来看看它所在的家族图谱:
从上图可以看出,HashMap继承自AbstractMap类并主要实现了Map接口,然而有一个地方很奇怪,既然AbstractMap已经实现了Map接口,为什么HashMap还要再实现一遍呢?网上普遍流传的说法是写法上的错误,然后就一直遗留了下来也没有改(0.0)具体可参照(https://blog.youkuaiyun.com/u011392897/article/details/60141739)。不过既然这样,也不深究了,继续向下看:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>,Cloneable,Serializable
由上面代码可知,HashMap除了继承自AbstractMap类,还实现了Cloneable和Serializable这两个接口,至于这两个接口的作用,在ArrayList讲解的那一篇已经说过了(移步ArrayList),就是克隆和序列化,不再赘述。接下来看看它的成员变量:
private static final long serialVersionUID = 362498820763181265L;
//序列化标志,用来反序列化的
static final int default_initial_capacity = 1 << 4;
//设置默认初始化容量为16
static final int maximum_capacity = 1 << 30;
//设置最大容量为2的三十次方
static final float default_load_factor = 0.75f;
//设置默认加载因子为0.75,用于每次何时扩容的判定
static final int treeify_threshold = 8;
//由链表转换为树的阈值,如果链表中的结点数量大于等于treeify_threshold
//则在链表的基础上建立红黑树
static final int untreeify_threshold = 6;
//由树转换为链表的阈值,如果红黑树中的结点小于等于untreeify_threshold
//则将红黑树拆分成链表
static final int min_treeify_capacity = 64;
//最小树化容量,当动态数组的长度大于等于min_treeify_capacity,则在相应的
//哈希表桶内对链表上建立红黑树
transient Node<K,V>[] table;
//成员变量,一个结点数组,不可序列化,即哈希表
transient Set<Map.Entry<K,V>> entrySet;
//成员变量,创建一个set容器,容器中放entry元素,不可序列化
transient int size;
//成员变量,不可序列化,HashMap中的键值对个数
transient int modCount;
//成员变量,不可序列化,老朋友了,控制同步的
int threshold;
//阈值
final float loadFactor;
//加载因子
这么多成员变量中最主要的是table,这是一个装载Node<K,V>类型的数组,因此HashMap的底层数据结构其实也可以说是数组,当然数组也只是它数据结构的一部分,至于其他部分是什么,也应该猜到了,链表加红黑树。那么回过头来,这个Node类型究竟是什么呢?
static class Node<K,V> implements Map.Entry<K,V>{
//内部静态类,定义结点
final int hash;
//哈希值
final K key;
//键值
V value;
//内容
Node<K,V> next;
//下一个结点
Node(int hash, K key, V value, Node<K,V> next){
//结点构造器
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey(){
return key;
}
public final V getValue(){
return value;
}
public final String toString(){
return key + "=" + value;
}
public final int hashCode(){
//重写Node结点的hashCode方法
//获得key-value的哈希值,自定义哈希值
return Objects.hashCode(key) ^ Objects.hashCode(value);
//返回调用Objects.hashCode方法得出key的哈希值与value的哈希值的异或运算
}
public final V setValue(V newValue){
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o){
//重写equals方法,用于比较两个键值对是否相等
if(o == this)
//如果两个的引用都指向一个对象,则说明相等,返回true
return true;
if(o instanceof Map.Entry){
//否则先判断是否实现了Map.Entry接口
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
//然后再比较两个键值对的key值和value值,如果两者都相等,才返回true
if(Objects.equals(key,e.getKey()) && Objects.equals(value,e.getValue()))
return true;
}
return false;
}
}
由上面代码可知,Node<K,V>是HashMap中的一个成员内部类,其实现了Map接口中的子接口Entry<K,V>,再看一看这个接口都有哪些方法:
HashMap中的Node<K,V>只实现了它的一部分方法,再来看看它的成员变量,即hash值,key值,value值,next结点。因此可以得知它是一个可以存储键值对以及其hash值的结点。而table又是一个存储此类结点的数组。既然是数组,那么有些变量如size,modCount,capacity之类的成员变量就容易理解了。那么另外的loadFactor、threshold又是什么意思呢?我们知道HashMap是用哈希表来存储数据的,也就是当插入键值对的时候,会先根据传进来的键值对的哈希值计算它在哈希表中所在的位置,那么按照一般数组的思路,只有当数组满的时候,才会进行扩容操作。那么哈希表适不适合这个思路呢?显然不适合,因为很有可能出现这种情况:哈希表还有几个位置是空的,但是绝大多数位置都满了,这达不到扩容的要求,但是插入键值对时有很大的可能性会发生哈希碰撞,这样极大的降低效率。因为为了尽量避免这种情况的发生,HashMap中设定了threshold(阈值)这个变量,当哈希表没有满但是其中的键值对个数大于等于阈值时就引发扩容操作。那么threshold又是怎么设定的呢?HashMap中规定threshold = loadFactor * capacity。并且通过研究与实践发现,loadFactor设定在0.75这个值效率最高(大了浪费时间,小了浪费空间)。因此默认设置loadFactor(加载因子)为0.75,当然你在创建HashMap时也可以自己设定这个值。
成员变量介绍到此,接下来介绍构造器:
public HashMap(int initialCapacity, float loadFactor){
//构造器1,终于看到认识的了,前面的把我看懵了
//传入初始容量与加载因子
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);
//初始化,但是只是设置了加载因子和阈值,阈值是大于初始容量的最接近2的整数次幂的数值
}
public HashMap(int initialCapacity){
//构造器2,只传入一个初始容量,则调用构造器1,并将
//加载因子设置为默认的
this(initialCapacity,default_load_factor);
}
public HashMap(){
//构造器3,如果传入空参,那么只设置加载因子为默认值
//默认初始容量是16
this.loadFactor = default_load_factor;
}
public HashMap(Map<? extends K, ? extends V> m){
//构造器4,传入一个map容器
this.loadFactor = default_load_factor;
//设置加载因子为默认加载因子
putMapEntries(m,false);
//调用方法将map容器加进来
}
从上述源码可知,HashMap中有四个构造器,当传入的是初始容量和加载因子时,构造器1设定的是阈值和加载因子,并没有直接创建相应容量的数组。同理,当传入的是初始容量时,构造器2调用构造器1,并将加载因子设定为默认的。当传入空参时也只是将加载因子设定为默认的。当传入一个Map集合时,构造器4先设定默认的加载因子,再调用相应的方法将该map容器中的键值对复制进来。因此我们可以知道,当我们创建一个HashMap时,系统是没有创建相应容量的数组的,这时候数组还没有初始化。那数组什么时候初始化呢?很明显,此时不初始化,那就只能是扩容时进行初始化了。因此我们再来看看HashMap的扩容方法:
final Node<K,V>[] resize(){
//扩容函数
Node<K,V>[] oldTab = table;//将HashMap中的数组作为老数组
int oldCap = (oldTab == null) ? 0 : oldTab.length;//获得老数组的容量
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 && oldCap >= default_initial_capacity)
//如果没有那么大,则将新的容量定位原容量的两倍,再判断新的容量是否小于最大容量并且就容量
//是否大于等于默认初始容量
//如果都满足则将新阈值也提高为旧阈值的两倍
newThr = oldThr << 1;
}else if(oldThr > 0)
//如果数组还没有初始化,查看阈值是否初始化
//如果阈值初始化了,则将新的容量定义为老的阈值
newCap = oldThr;
else{
//如果阈值也没有初始化,则将容量等于默认初始容量
//将新阈值设定为默认加载因子*默认初始容量
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);
//如果新阈值等于0,说明新阈值溢出了
//则重新计算新数组中的阈值,如果新容量小于最大容量且计算后的阈值小于最大容量
//就将新阈值设置为计算后的阈值,否则就设定为整数类型的最大值
}
threshold = newThr;
//将新阈值设置为当前阈值
//以上是设置新的动态数组的容量和加载因子
//下面就是将旧数组的键值对复制到新数组中
@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,再判断e是否为空,如果不为空,继续进行
oldTab[j] = null;//将旧数组结点引用赋值为null,方便GC收集
if(e.next == null)//如果该结点没有额外的引用,说明该位置只有一个键值对
newTab[e.hash & (newCap - 1)] = e;//直接将e放入新数组中,注意是根据哈希值重新定位位置
else if(e instanceof TreeNode)//如果还有额外的引用,则判断e结点是否为红黑树中的结点
((TreeNode<K,V>)e).split(this,newTab,j,oldCap);//是的话进行红黑树中的扩容操作
else{
//如果结点不是红黑树中的结点,则说明该位置中放的是双向链表的首结点
Node<K,V> loHead = null, loTail = null;//定义第一个双向链表
Node<K,V> hiHead = null, hiTail = null;//定义第二个双向链表
Node<K,V> next;//定义下一个结点指针
do{
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;
//再将第一条链表的表头放入新数组的原索引位置
}
if(hiTail != null){
//如果第二个链表不为空
hiTail.next = null;
newTab[j + oldCap] = hiHead;
//将第二条链表的表头放入新数组的新索引(原索引+旧数组容量)的位置
}
}
}
}
}
return newTab;//返回一个新数组
}
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit){
//扩容方法,index表示扩容前哈希表的索引,bit表示扩容前哈希表的容量
//当原数组扩容时发现数组索引处放的是红黑树根节点的时候调用此方法
TreeNode<K,V> b = this;//b为调用此方法的结点
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;
if((e.hash & bit) == 0){
//如果结点的哈希值和新容量相与为0,表示该结点扩容后也在这个位置
if((e.prev = loTail) == null)
//将e的前置结点设置为尾结点,如果为空
loHead = e;
//则将e作为首结点
else
//否则将尾结点的后置结点设置为e
loTail.next = e;
loTail = e;
//再将尾结点移动到e结点位置,从而完成了增加一个结点的任务
++lc;
//第一个链表结点个数加一
}
else {
//如果不为0,由于哈希表扩容为原来的两倍,所以必在另外一个槽内
if((e.prev = hiTail) == null)
//同样,将e的前置结点设置为尾结点,如果尾结点为空
hiHead = e;
//将e设置为头结点
else
hiTail.next = e;
//否则尾结点的后置结点为e
hiTail = e;
//尾结点移动到e处
++hc;
//第二个链表结点个数加一
}
}
//将红黑树拆分成两个链表成功
if(loHead != null){
//如果第一个链表的头结点不为空,说明链表中有结点
if(lc <= untreeify_threshold)
//如果链表内结点个数小于建树阈值
tab[index] = loHead.untreeify(map);
//则将红黑树拆成链表并将表头放入哈希表槽内
else{
//否则,说明表内结点个数大于建树阈值
tab[index] = loHead;
//先将表头放入哈希表槽内
if(hiHead != null)
//如果另外一个表为空,说明结点都在这个表内了,不需要再建树
//本来就是树,如果另外一个表不为空,说明树结构破坏了,所以
//重新建树
loHead.treeif(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);
//否则,重新建树
}
}
}
扩容函数有两个部分,第一个部分是在数组和链表的基础上进行扩容的,也就是先重新设定容量与阈值,然后建立新数组,遍历旧数组,而遍历到的结点可分为三种类型:
1.普通结点,就是一个普通的键值对,则按照哈希值重新定位放到新数组相应的位置就行。
2.链表上的结点,则在数组中的是双向链表的头结点,那么就要根据该键值对的hash值&数组原容量来确定它是放在原位置还是移动一个原数组容量大小的位置了。
3.红黑树上的结点,则在数组中的是红黑树的根节点,那么就要根据该键值对的hash值&数组原来确定它是放在原位置还是移动一个原数组容量大小的位置,由于将其分为两份了,所以还要根据每一份的键值对个数来判断是要拆树还是重新建树。
那么问题来了,为什么该键值对的hash值&数组原容量就可以判断它在新数组中的具体位置呢?也就是这条代码:
(e.hash & oldCap) == 0
那么我们先来分析一下,我们开始是怎样将键值对通过其hash值放入到数组中特定位置的,我们知道是通过table[e.hash & (table.length - 1)] = e,将键值对放进去的,假设该数组容量为16(默认初始容量),比如有哈希值分别为2,34,50这三个键值对:
将2放进去:
0000 0000 0000 0000 0000 0000 0000 1111
0000 0000 0000 0000 0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0010
结果为2,因此放入table[2]中。
将34放进去:
0000 0000 0000 0000 0000 0000 0000 1111
0000 0000 0000 0000 0000 0000 0010 0010
0000 0000 0000 0000 0000 0000 0000 0010
结果还是2,因此将其作为一个双向链表的结点作为table[2]中的结点的后置结点
再将50放进去:
0000 0000 0000 0000 0000 0000 0000 1111
0000 0000 0000 0000 0000 0000 0011 0010
0000 0000 0000 0000 0000 0000 0000 0010
结果为2,将其作为34结点的后置结点。
那么扩容时,当遍历到table[2]中的结点,发现它是双向链表的头结点,于是调用语句hash&table.length,table.length = 16.
分离2:
0000 0000 0000 0000 0000 0000 0001 0000
0000 0000 0000 0000 0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0000
分离34:
0000 0000 0000 0000 0000 0000 0001 0000
0000 0000 0000 0000 0000 0000 0010 0010
0000 0000 0000 0000 0000 0000 0000 0000
分离50:
0000 0000 0000 0000 0000 0000 0001 0000
0000 0000 0000 0000 0000 0000 0011 0010
0000 0000 0000 0000 0000 0000 0001 0000
由上面可知,任何数和16进行与运算,要么为0,要么为16,所以可以根据这个将链表或红黑树中的结点分为两类。那么为什么可以确定等于0的就一定在新数组的原位置,等于16的就一定在新数组的原索引+旧数组容量的位置上呢?我们拿34和50来实验一下:
重装34:
0000 0000 0000 0000 0000 0000 0001 1111
0000 0000 0000 0000 0000 0000 0010 0010
0000 0000 0000 0000 0000 0000 0000 0010
结果为2.
重装50:
0000 0000 0000 0000 0000 0000 0001 1111
0000 0000 0000 0000 0000 0000 0011 0010
0000 0000 0000 0000 0000 0000 0001 0010
结果为16 + 2.
因为新数组确定位置的散列函数是index = hash & 31。而键值对的hash值与15进行与运算和与31进行与运算只有表示16的那个二进制位不同,如果分离时该位是1,那么与新数组进行散列函数计算时该位也一定是1,反之也一样。所以可以很快进行定位(因为每一次扩容必定是前一次容量的两倍,而初始容量又不让你自己定,所以才能实现这种功能)。
接下来再看看HashMap中的常用功能:
1.增加键值对
public V put(K key, V value){
//向HashMap加入键值对
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){
//将键值对插入到HashMap中
Node<K,V>[] tab; Node<K,V> p; int n, i;
if((tab = table) == null || (n = tab.length) == 0)
//如果动态数组还没有创建,那就开始创建
n = (tab = resize()).length;
if((p = tab[i = (n - 1) & hash]) == null)
//如果哈希值寻址寻到的动态数组结点处还是空的,那么创建一个新结点加进去
tab[i] = newNode(hash,key,value,null);
else{
Node<K,V> e; K k;
//到此处,哈希寻址已经初步确定了需要插入的地方在动态数组中的结点位置,但是
//该结点已经有键值对了,所以判断是直接替换该键值对还是向外延伸(红黑树或链表)
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){
//开始遍历链表,找到链表尾,并且用binCount计算链表的长度
if((e = p.next) == null){
//找到链表尾,在尾部加入一个新结点
p.next = newNode(hash, key, value, null);
if(binCount >= treeify_threshold - 1)//判断链表如果大于等于阈值-1,则开始建树
treeifyBin(tab,hash);
break;
}
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;//如果找到链表中有一个结点的哈希值和键一样,直接跳出循环
p = e;//第一个if判断里面已经有一个e = p.next,而且一定会执行,这令人窒息的操作...
//因此这里只要p = e就可以进行链表向后推
}
}
if(e != null){
//如果e不是新的结点,则在结点处修改
V oldValue = e.value;//获得原结点的值
if(!onlyIfAbsent || oldValue == null)//这里的onlyIfAbsent的意思是如果为空则执行
//即如果该键对应的地方是null,就将值放入,否则就不放,直接返回,而put将onlyIfAbsent
//设置为false,所以此处的意思是直接放,不管它是否为空
e.value = value;//即更新值
afterNodeAccess(e);//做一些改变....
return oldValue;//返回原结点的值
}
}
++modCount;//如果插入的是一个新的结点
if(++size > threshold)
resize();//增加容量
afterNodeInsertion(evict);//整理,HashMap中没有进行实现,为LinkedHashMap留后路
return null;//返回原值,也就是null
}
2.删除键值对
public V remove(Object key){
//根据键删除某个结点
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null
? null
: e.value;
}
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;//找到了在动态数组上的位置,如果键相等,则将p的引用直接给node
else if((e = p.next) != null){
//如果键不相等,说明哈希寻址错误,查看该结点是否还有链接,如果有,继续寻址下去
if(p instanceof TreeNode)//先判断是否建树,p结点是否是红黑树上的一个结点
node = ((TreeNode<K,V>p).getTreeNode(hash,key);//如果是,则在树上寻找到结点引用给node
else {
do{//如果不是,则说明在链表上,遍历链表
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
node = e;//如果链表上存在结点信息一直,则将引用给node,并跳出循环
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;
else
p.next = node.next; //否则,就在链表上,则将前一个结点的下一个指针指向后一个结点
++modCount;//计数值增加
--size;//数量减一
afterNodeRemoval(node);//后续处理,HashMap中未定义
return node;//返回被删除的结点
}
}
return null;//如果没找到就返回null
}
3.查找键值对
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, e; int n; K k;
if((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null){
//先判断动态数组是否为空,如果不为空则判断哈希值和数组长度相与
//获得的数组索引处是否为空,如果为空则说明此处没有键值对,直接返回
//空,否则继续探询。
if(first.hash == hash && ((k = first.key) == key
|| (key != null && key.equals(k))))
//判断结点的哈希值和传进来的哈希值相等并且
//结点的键和传进来的键一样,说明要找的键值对就在
//动态数组中,一次中,返回first即为要找的值
return first;
if((e = first.next) != null){
//如果第一次哈希寻址失败,则先判断该结点有没有链连接到别的结点上
//因为HaashMap存储元素是先在动态数组上找位置,如果位置被占了就在
//改位置处引出一条连接,指向红黑树或者链表
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;
}
4.是否包含
public boolean containsKey(Object key){
//判断HashMap中是否含有某个键,调用getNode方法就行
return getNode(hash(key),key) != null;
}
public boolean containsValue(Object value){
//查看HashMap中是否含有某一个值
Node<K,V>[] tab; V v;
if((tab = table) != null && size > 0){
//先判断哈希表是否是空的,如果不是,则遍历哈希表上和表外的每一个结点
//如果找到了一样的值,就跳出循环,返回true。
for(int i = 0; i < tab.length; ++i){
for(Node<K,V> e = tab[i]; e != null; e = e.next){
if((v = e.value) == value || (value != null && value.equals(v)))
return true;
}
}
}
//如果没找到就返回false
return false;
}
5.获得键值对个数
public int size(){
//获得Hashmap中键值对的个数
return size;
}
6.其他方法
public boolean isEmpty(){
//查看HashMap中是否为空
return size == 0;
}
public void clear(){
//哈希表的清空功能就很简单,将哈希表的每一个结点引用都置为空就够了
Node<K,V>[] tab;
modCount++;
if((tab = table) != null && size > 0){//先判断哈希表是否为空,如果为空就没必要清空了
size = 0;
for(int i = 0; i < tab.length; ++i)
tab[i] == null;
}
}
public Set<K> keySet(){
//获得一个Set容器
Set<K> ks = keySet;
if(ks == null){
ks = new KeySet();
keySet = ks;
}
return ks;
}
public Collection<V> values(){
//获得所有值组成的集合
Collection<V> vs = values;
if(vs == null){
vs = new Values();
values = vs;
}
return vs;
}
源码解析
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>,Cloneable,Serializable
{
private static final long serialVersionUID = 362498820763181265L;
//序列化标志,用来反序列化的
static final int default_initial_capacity = 1 << 4;
//设置默认初始化容量为16
static final int maximum_capacity = 1 << 30;
//设置最大容量为2的三十次方
static final float default_load_factor = 0.75f;
//设置默认加载因子为0.75,用于每次何时扩容的判定
static final int treeify_threshold = 8;
//由链表转换为树的阈值,如果链表中的结点数量大于等于treeify_threshold
//则在链表的基础上建立红黑树
static final int untreeify_threshold = 6;
//由树转换为链表的阈值,如果红黑树中的结点小于等于untreeify_threshold
//则将红黑树拆分成链表
static final int min_treeify_capacity = 64;
//最小树化容量,当动态数组的长度大于等于min_treeify_capacity,则在相应的
//哈希表桶内对链表上建立红黑树
static class Node<K,V> implements Map.Entry<K,V>{
//内部静态类,定义结点
final int hash;
//哈希值
final K key;
//键值
V value;
//内容
Node<K,V> next;
//下一个结点
Node(int hash, K key, V value, Node<K,V> next){
//结点构造器
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey(){
return key;
}
public final V getValue(){
return value;
}
public final String toString(){
return key + "=" + value;
}
public final int hashCode(){
//重写Node结点的hashCode方法
//获得key-value的哈希值,自定义哈希值
return Objects.hashCode(key) ^ Objects.hashCode(value);
//返回调用Objects.hashCode方法得出key的哈希值与value的哈希值的异或运算
}
public final V setValue(V newValue){
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o){
//重写equals方法,用于比较两个键值对是否相等
if(o == this)
//如果两个的引用都指向一个对象,则说明相等,返回true
return true;
if(o instanceof Map.Entry){
//否则先判断是否实现了Map.Entry接口
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
//然后再比较两个键值对的key值和value值,如果两者都相等,才返回true
if(Objects.equals(key,e.getKey()) && Objects.equals(value,e.getValue()))
return true;
}
return false;
}
}
static final int hash(Object key){
//设定哈希值,判断键是否为空,如果为空,哈希值为0
//否则哈希值为键的哈希值与键的哈希值无符号右移16位的值
//异或得到的结果
int h;
return (key == null) ? 0 : (h == key.hashCode()) ^ (h >>> 16);
}
static Class<?> comparableClassFor(Object x){
//此方法是根据传进来的一个对象,如果它类型实现了Comparable接口,则
//获取它的运行时类型并返回
if(x instanceof Comparable){
//首先判断x是否实现了Comparable接口,instanceof可以理解为是某种类型的实例
//无论是运行时类型,还是它的父类,它实现的接口,它父类实现的接口,只要在继承链
//上有这个类型就行,说人话就是instanceof从左边的对象所属的类开始,一直延伸到继承
//家族的最顶端,只要这里面包含了instanceof右边的类或接口,那么就会返回true
Class<?> c; Type[] ts,as; Type t; ParameterizedType p;
if((c = x.getClass()) == String.class)
//getClass方法是获得对应的运行时类型,即new它的时候的类型,判断其运行时类型
//是否为String类型,如果是,则直接返回其运行时类型
return c;
if((ts = c.getGenericInterfaces()) != null){
//否则,先用ts获得x的运行时类型直接实现的接口(必须是自己实现的,继承过来的不算)
//即判断是否有直接实现的接口
for(int i = 0; i < ts.length; ++i){
//遍历直接实现的接口
if(((t = ts[i]) instanceof ParameterizedType) &&
//如果某一个接口实现了泛型
((p = (parameterizedType)t).getRawType() == Comparable.class) &&
//则获取该接口不带参数部分的类型对象并确认该类型是Comparable
(as = p.getActualTypeArguments()) != null &&
//再获取泛型参数数组,确保数组不为空
as.length == 1 && as[0] == c)
//确认该数组只有一个泛型参数且该实现类型是该类型本身
return c;
//则返回该对象的运行时类型c
}
}
}
return null;
//否则返回null
}
@SuppressWarnings({"rawtypes","unchecked"})//rawtypes指的是传参时也要传递带泛型的参数
static int compareComparables(Class<?> kc, Object k, Object x){
//传入的kc是k的class对象,用于比较传进来的x和k是不是同一个类
//如果x为空或者两个对象的类不一样,则返回0,否则进行同类的比较
//返回两个对象比较的结果
return (x == null || x.getClass() != kc
? 0
: ((comparable)k).compareTo(x));
}
static final int tableSizeFor(int cap){
//返回大于输入参数且最接近的2的整数次幂的结果
int n = cap - 1;
n |= n >>> 1;//看起来很陌生?那看这个吧: n = n | n >>> 1,现在熟悉了吧
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
//总共向右移了31位,int型是32位,但是为什么要分开移呢?
return (n < 0) ? 1 : (n >= maximum_capacity) ? maximum_capacity : n + 1;
}
transient Node<K,V>[] table;
//成员变量,一个结点数组,不可序列化,即哈希表
transient Set<Map.Entry<K,V>> entrySet;
//成员变量,创建一个set容器,容器中放entry元素,不可序列化
transient int size;
//成员变量,不可序列化,HashMap中的键值对个数
transient int modCount;
//成员变量,不可序列化,老朋友了,控制同步的
int threshold;
//阈值
final float loadFactor;
//加载因子
public HashMap(int initialCapacity, float loadFactor){
//构造器1,终于看到认识的了,前面的把我看懵了
//传入初始容量与加载因子
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);
//初始化,但是只是设置了加载因子和阈值,阈值是大于初始容量的最接近2的整数次幂的数值
}
public HashMap(int initialCapacity){
//构造器2,只传入一个初始容量,则调用构造器1,并将
//加载因子设置为默认的
this(initialCapacity,default_load_factor);
}
public HashMap(){
//构造器3,如果传入空参,那么只设置加载因子为默认值
//默认初始容量是16
this.loadFactor = default_load_factor;
}
public HashMap(Map<? extends K, ? extends V> m){
//构造器4,传入一个map容器
this.loadFactor = default_load_factor;
//设置加载因子为默认加载因子
putMapEntries(m,false);
//调用方法将map容器加进来
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict){
//将传入的Map容器m中的键值对复制到当前的HashMap中
int s = m.size();
//先获得m中的键值对个数
if(s > 0){
//如果m中含有键值对,开始复制
if(table == null){
//先判断当前的HashMap中的哈希表有没有初始化,如果没有则先初始化
float ft = ((float)s / loadFactor) + 1.0F;
//获得m的实际容量
int t = ((ft < (float)maximum_capacity)
? (int)ft
: maximum_capacity);
//判断m的实际容量是否大于规定的最大容量,如果大,则将t设定为
//最大容量,否则设置t为m的实际容量
if(t > threshold)
//判断所需的阈值是否大于当前阈值,如果大,则进行初始化
threshold = tableSizeFor(t);
}
else if(s > threshold)
//否则,说明已经初始化了,判断键值对个数是否大于阈值,大于则扩容
resize();
for(Map.Entry<? extends K, ? extends V> e : m.entrySet()){
//遍历m中的键值对,将其复制到当前HashMap中
K key = e.getkey();
V value = e.getValue();
putVal(hash(key),key,value,false,evict);
}
}
}
public int size(){
//获得Hashmap中键值对的个数
return size;
}
public boolean isEmpty(){
//查看HashMap中是否为空
return size == 0;
}
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, e; int n; K k;
if((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null){
//先判断动态数组是否为空,如果不为空则判断哈希值和数组长度相与
//获得的数组索引处是否为空,如果为空则说明此处没有键值对,直接返回
//空,否则继续探询。
if(first.hash == hash && ((k = first.key) == key
|| (key != null && key.equals(k))))
//判断结点的哈希值和传进来的哈希值相等并且
//结点的键和传进来的键一样,说明要找的键值对就在
//动态数组中,一次中,返回first即为要找的值
return first;
if((e = first.next) != null){
//如果第一次哈希寻址失败,则先判断该结点有没有链连接到别的结点上
//因为HaashMap存储元素是先在动态数组上找位置,如果位置被占了就在
//改位置处引出一条连接,指向红黑树或者链表
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;
}
public boolean containsKey(Object key){
//判断HashMap中是否含有某个键,调用getNode方法就行
return getNode(hash(key),key) != null;
}
public V put(K key, V value){
//向HashMap加入键值对
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){
//将键值对插入到HashMap中
Node<K,V>[] tab; Node<K,V> p; int n, i;
if((tab = table) == null || (n = tab.length) == 0)
//如果动态数组还没有创建,那就开始创建
n = (tab = resize()).length;
if((p = tab[i = (n - 1) & hash]) == null)
//如果哈希值寻址寻到的动态数组结点处还是空的,那么创建一个新结点加进去
tab[i] = newNode(hash,key,value,null);
else{
Node<K,V> e; K k;
//到此处,哈希寻址已经初步确定了需要插入的地方在动态数组中的结点位置,但是
//该结点已经有键值对了,所以判断是直接替换该键值对还是向外延伸(红黑树或链表)
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){
//开始遍历链表,找到链表尾,并且用binCount计算链表的长度
if((e = p.next) == null){
//找到链表尾,在尾部加入一个新结点
p.next = newNode(hash, key, value, null);
if(binCount >= treeify_threshold - 1)//判断链表如果大于等于阈值-1,则开始建树
treeifyBin(tab,hash);
break;
}
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;//如果找到链表中有一个结点的哈希值和键一样,直接跳出循环
p = e;//第一个if判断里面已经有一个e = p.next,而且一定会执行,这令人窒息的操作...
//因此这里只要p = e就可以进行链表向后推
}
}
if(e != null){
//如果e不是新的结点,则在结点处修改
V oldValue = e.value;//获得原结点的值
if(!onlyIfAbsent || oldValue == null)//这里的onlyIfAbsent的意思是如果为空则执行
//即如果该键对应的地方是null,就将值放入,否则就不放,直接返回,而put将onlyIfAbsent
//设置为false,所以此处的意思是直接放,不管它是否为空
e.value = value;//即更新值
afterNodeAccess(e);//做一些改变....
return oldValue;//返回原结点的值
}
}
++modCount;//如果插入的是一个新的结点
if(++size > threshold)
resize();//增加容量
afterNodeInsertion(evict);//整理,HashMap中没有进行实现,为LinkedHashMap留后路
return null;//返回原值,也就是null
}
final Node<K,V>[] resize(){
//扩容函数
Node<K,V>[] oldTab = table;//将HashMap中的数组作为老数组
int oldCap = (oldTab == null) ? 0 : oldTab.length;//获得老数组的容量
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 && oldCap >= default_initial_capacity)
//如果没有那么大,则将新的容量定位原容量的两倍,再判断新的容量是否小于最大容量并且就容量
//是否大于等于默认初始容量
//如果都满足则将新阈值也提高为旧阈值的两倍
newThr = oldThr << 1;
}else if(oldThr > 0)
//如果数组还没有初始化,查看阈值是否初始化
//如果阈值初始化了,则将新的容量定义为老的阈值
newCap = oldThr;
else{
//如果阈值也没有初始化,则将容量等于默认初始容量
//将新阈值设定为默认加载因子*默认初始容量
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);
//如果新阈值等于0,说明新阈值溢出了
//则重新计算新数组中的阈值,如果新容量小于最大容量且计算后的阈值小于最大容量
//就将新阈值设置为计算后的阈值,否则就设定为整数类型的最大值
}
threshold = newThr;
//将新阈值设置为当前阈值
//以上是设置新的动态数组的容量和加载因子
//下面就是将旧数组的键值对复制到新数组中
@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,再判断e是否为空,如果不为空,继续进行
oldTab[j] = null;//将旧数组结点引用赋值为null,方便GC收集
if(e.next == null)//如果该结点没有额外的引用,说明该位置只有一个键值对
newTab[e.hash & (newCap - 1)] = e;//直接将e放入新数组中,注意是根据哈希值重新定位位置
else if(e instanceof TreeNode)//如果还有额外的引用,则判断e结点是否为红黑树中的结点
((TreeNode<K,V>)e).split(this,newTab,j,oldCap);//是的话进行红黑树中的扩容操作
else{
//如果结点不是红黑树中的结点,则说明该位置中放的是双向链表的首结点
Node<K,V> loHead = null, loTail = null;//定义第一个双向链表
Node<K,V> hiHead = null, hiTail = null;//定义第二个双向链表
Node<K,V> next;//定义下一个结点指针
do{
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;
//再将第一条链表的表头放入新数组的原索引位置
}
if(hiTail != null){
//如果第二个链表不为空
hiTail.next = null;
newTab[j + oldCap] = hiHead;
//将第二条链表的表头放入新数组的新索引(原索引+旧数组容量)的位置
}
}
}
}
}
return newTab;//返回一个新数组
}
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)
//先判断动态数组的状态,如果没有初始化或者小于最小树化阈值,则扩容就行了
resize();
else if((e = tab[index = (n - 1) & hash]) != null){
//如果数组大小大于等于树化阈值且该结点处不是空的,则在该结点处开始建树
TreeNode<K,V> hd = null, tl = null;
do{
TreeNode<K,V> p = replacementTreeNode(e, null);
//将Node全都转换为TreeNode并且重新遍历建立双向链表
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);
}
}
public void putAll(Map<? extends K, ? extends V> m){
//将一个map集合放进来
putMapEntries(m,true);
}
public V remove(Object key){
//根据键删除某个结点
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null
? null
: e.value;
}
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;//找到了在动态数组上的位置,如果键相等,则将p的引用直接给node
else if((e = p.next) != null){
//如果键不相等,说明哈希寻址错误,查看该结点是否还有链接,如果有,继续寻址下去
if(p instanceof TreeNode)//先判断是否建树,p结点是否是红黑树上的一个结点
node = ((TreeNode<K,V>p).getTreeNode(hash,key);//如果是,则在树上寻找到结点引用给node
else {
do{//如果不是,则说明在链表上,遍历链表
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
node = e;//如果链表上存在结点信息一直,则将引用给node,并跳出循环
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;
else
p.next = node.next; //否则,就在链表上,则将前一个结点的下一个指针指向后一个结点
++modCount;//计数值增加
--size;//数量减一
afterNodeRemoval(node);//后续处理,HashMap中未定义
return node;//返回被删除的结点
}
}
return null;//如果没找到就返回null
}
public void clear(){
//哈希表的清空功能就很简单,将哈希表的每一个结点引用都置为空就够了
Node<K,V>[] tab;
modCount++;
if((tab = table) != null && size > 0){//先判断哈希表是否为空,如果为空就没必要清空了
size = 0;
for(int i = 0; i < tab.length; ++i)
tab[i] == null;
}
}
public boolean containsValue(Object value){
//查看HashMap中是否含有某一个值
Node<K,V>[] tab; V v;
if((tab = table) != null && size > 0){
//先判断哈希表是否是空的,如果不是,则遍历哈希表上和表外的每一个结点
//如果找到了一样的值,就跳出循环,返回true。
for(int i = 0; i < tab.length; ++i){
for(Node<K,V> e = tab[i]; e != null; e = e.next){
if((v = e.value) == value || (value != null && value.equals(v)))
return true;
}
}
}
//如果没找到就返回false
return false;
}
public Set<K> keySet(){
//获得一个Set容器
Set<K> ks = keySet;
if(ks == null){
ks = new KeySet();
keySet = ks;
}
return ks;
}
final class KeySet extends AbstractSet<K>{
//内部类,相当于键的一个不重复数组
public final int size(){
return size;//获得HashMap的大小
}
public final void clear(){
HashMap.this.clear();//清空HashMap
}
public final Iterator<K> iterator(){
return new KeyIterator();//获得键的迭代器
}
public final boolean contains(Object o){
return containsKey(o);//判断是否含有某个键
}
public final boolean remove(Object key){//删除含有该键的某个结点
return removeNode(hash(key), key, null, false,true) != null;
}
public final Spliterator<K> spliterator(){//获得分割器
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action){
Node<K,V>[] tab;//forEach,1.8新特性,主要是遍历键
if(action == null)
throw new NullPointerException();
if(size > 0 && (tab = table) != null){
int mc = modCount;
for(int i = 0; i < tab.length; ++i){
for(Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e,key);
}
if(modCount != mc)
throw new ConcurrentModificationException();
}
}
}
public Collection<V> values(){
//获得所有值组成的集合
Collection<V> vs = values;
if(vs == null){
vs = new Values();
values = vs;
}
return vs;
}
final class Values extends AbstractCollection<V>{
//内部类,用于操作HashMap中的值
public final int size(){
return size;//获得HashMap的大小
}
public final void clear(){
HashMap.this.clear();//清空HashMap
}
public final Iterator<V> iterator(){
return new ValueIterator();//获得值迭代器
}
public final boolean contains(Object o){
return containsValue(o);//查询是否含有某个值
}
public final Spliterator<V> spliterator(){//获得分割器
return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super V> action){
Node<K,V>[] tab;//遍历
if(action == null)
throw new NullPointerException();
if(size > 0 && (tab = table) != null){
int mc = modCount;
for(int i = 0; i < tab.length; ++i){
for(Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if(modCount != mc)
throw new ConcurrentModificationException();
}
}
}
public Set<Map.Entry<K,V>> entrySet(){
//获得装有键值对的Set集合
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
public class EntrySet extends AbstractSet<Map.Entry<K,V>>{
//内部类,和上面两个类似,只是将键和值换成了键值对
public final int size(){
return size;
}
public final void clear(){
HashMap.this.clear();
}
public final Iterator<Map.Entry<K,V>> iterator(){
return new EntryIterator();
}
public final boolean contains(Object o){
if(!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o){
if(o instanceof Map.Entry){
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
Object key = e.getkey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator(){
return new EntrySpliterator<>(HashMap.this,0 ,-1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action){
Node<K,V>[] tab;
if(action == null)
throw new NullPointerException();
if(size > 0 && (tab = table) != null){
int mc = modCount;
for(int i = 0; i < tab.length; ++i){
for(Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if(modCount != mc)
throw new ConcurrentModificationException();
}
}
}
@Override
public V getOrDefault(Object key, V defaultValue){
//使用key去HashMap中寻找结点,如果找到了就返回结点的值,否则就返回传入的defaultValue
Node<K,V> e;
return (e = getNode(hash(key),key)) == null ? defaultValue : e.value;
}
@Override
public V putIfAbsent(K key,V value){
//如果该键值对应的结点是空,则将value放入其对应的值的位置,否则不放
return putVal(hash(key),key,value,true,true);
}
@Override
public boolean remove(Object key, Object value){
//删除某个结点
return removeNode(hash(key), key, value, true, true) != null;
}
@Override
public boolean replace(K key, V oldValue, V newValue){
//更新,通过键找到结点,将旧值换成新值
Node<K,V> e; V v;
if((e = getNode(hash(key), key)) != null && ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))){
//先寻找到结点,再判断该结点的值是否等于传入的旧的值,是则替换成新值,并返回true
e.value = newValue;
afterNodeAccess(e);
return true;
}
return false;//否则返回false;
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction){
//java1.8新特性,寻找到key值对应的结点,将如果该结点的值不为空,直接返回原结点的值
//如果结点的值为空,则将新值插入进去并返回新值
if(mappingFuntion == null)
throw new NullPointException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n,i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
if(size > threshold || (tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if((first = tab[i = (n - 1) & hash]) != null){
if(first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash,key);
else {
Node<K,V> e = first; K k;
do{
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
old = e;
break;
}
++binCount;
}while((e = e.next) != null);
}
V oldValue;
//如果old节点不是空而且该结点的值也不为空,则直接返回该结点的值
if(old != null && (oldValue = old.value) != null){
afterNodeAccess(old);
return oldValue;
}
}
//查找到了key值对应的空结点
V v = mappingFunction.apply(key);
if(v == null){
return null;//如果传进来的是空值,则返回空
}else if(old != null){
old.value = v;//结点为空,直接放入,并且返回新值
afterNodeAccess(old);//这是什么??
return v;
}
else if(t != null)
t.putTreeVal(this, tab, hash, key, v);//放在红黑树中
else {
tab[i] = newNode(hash, key, v, first);//放在动态哈希表中
if(binCount >= treeify_threshold - 1)
treeifyBin(tab,hash);
}
++modCount;
++size;
afterNodeInsertion(true);//这又是什么?
return v;
}
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction){
//和computeIfAbsent相反,只有当key存在时才进行操作
if(remappingFunction == null)
throw new NullPointerException();
Node<K,V> e; V oldValue;
int hash = hash(key);
if((e = getNode(hash,key)) != null && (oldValue = e.value) != null){
V v = remappingFunction.apply(key,oldValue);
if(v != null){//如果操作中有值则将其赋给旧值
e.value = v;
afterNodeAccess(e);
return v;
}else//如果没有值,即值为null,则删除该结点
removeNode(hash, key, null, false, true);
}
return null;
}
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction){
//无论key值存不存在,都进行操作
if(remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n,i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
if(size > threshold || (tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if((first = tab[i = (n - 1) & hash]) != null){
if(first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash,key);
else{
Node<K,V> e = first; K k;
do{
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
old = e;
break;
}
++binCount;
}while((e = e.next) != null);
}
}
V oldValue = (old == null) ? null : old.value;
//找到所需结点
V v = remappingFunction.apply(key, oldValue);
//获得操作
if(old != null){
//如果该结点的值不为空,则进行修改操作
if(v != null){
old.value = v;
afterNodeAcess(old);
}
else
removeNode(hash, key, null, false, true);
}
else if(v != null){
//如果结点的值为空,则进行插入操作
if(t != null){
t.putTreeVal(this, tab, hash, key, v);
}
else{
tab[i] = newNode(hash, key, v, first);
if(binCount >= treeify_threshold - 1)
treeifyBin(tab,hash);
}
++modCount;
++size;
afterNodeInsertion(true);
}
return v;
}
@Override
public V merge(K key,V value,BinFunction<? super V, ? super V, ?extends V> remappingFunction){
//和compute类似,不过传的是value,如果原值为空或结点不存在,可以将传入的value作为值插入或修改
if(value == null)
throw new NullPointerException();
if(remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n,i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
if(size > threshold || (tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if((first = tab[i = (n - 1) & hash]) != null){
if(first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash,key);
else{
Node<K,V> e = first;
K k;
do{
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
old = e;
break;
}
++binCount;
}while((e = e.next) != null);
}
}
if(old != null){
V v;
if(old.value != null)
v = remappingFunction.apply(old.value, value);
else
v = value;
if(v != null){
old.value = v;
afterNodeAccess(old);
}
else
removeNode(hash, key, null, false, true);
return v;
}
if(value != null){
if(t != null)
t.putTreeVal(this,tab,hash,key,value);
else{
tab[i] = newNode(hash, key, value, first);
if(binCount >= treeify_threshold - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
}
return value;
}
@Override
public void forEach(BiConsumer<? super K, ? super V>action){
//遍历所有结点
Node<K,V>[] tab;
if(action == null)
throw new NullPointerException();
if(size > 0 && (tab = table) != null){
int mc = modCount;
for(int i = 0; i < tab.length; ++i){
for(Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key, e.value);
}
if(modCount != mc)
throw new ConcurrentModificationException();
}
}
@Override
public void replaceAll(BiFunction<? super K,? super V, ? extends V>function){
//对所有的结点进行相关操作
Node<K,V>[] tab;
if(function == null)
throw new NullPointerException();
if(size > 0 && (tab = table) != null){
int mc = modCount;
for(int i = 0; i < tab.length; ++i){
for(Node<K,V> e = tab[i]; e != null; e = e.next){
e.value = function.apply(e.key, e.value);
}
}
if(modCount != mc)
throw new ConcurrentModificationException();
}
}
@SuppressWarnings("unchecked")
@Override
public Object clone(){
//克隆HashMap
HashMap<K,V> result;
try{
result = (HashMap<K,V>)super.clone();
}catch(CloneNotSupportedException e){
throw new InternalError(e);
}
result.reinitialize();
result.putMapEntries(this,false);
return result;
}
final float loadFactor(){
//获取加载因子
return loadFactor;
}
final int capacity(){
//返回的是哈希表的表长
return (table != null) ? table.length :(threshold > 0) ? threshold : default_initial_capacity;
}
private void writeObject(java.io.ObjectOutputStream s) throws IOException{
//写入操作
int buckets = capacity();
s.defaultWriteObject();
s.writeInt(buckets);
s.writeInt(size);
internalWriteEntries(s);
}
private void readObject(java.io.ObjectInputStream s) throws IOException,ClassNotFoundException{
//读出操作
s.defaultReadObject();
reinitialize();
if(loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor :" + loadFactor);
s.readInt();
int mappings = s.readInt();
if(mappings < 0)
throw new InvalidObjectException("Illegal mappings count:" + mappings);
else if(mappings > 0)
float lf = Math.min(Math.max(0.25,loadFactor),4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < default_initial_capacity)
? default_initial_capacity
: (fc >= maximum_capacity)
? maximum_capacity
: tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < maximum_capacity && ft < maximum_capacity)
? (int)ft : Integer.MAX_VALUE);
SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class,cap);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;
for(int i = 0; i < mappings; i++){
@SuppressWarnings("unchecked")
K key = (K) s.readObject;
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
abstract class HashIterator{
//抽象内部类,哈希迭代器
Node<K,V> next;
Node<K,V> current;
int expectedModCount;
int index;
HashIterator(){
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if(t != null && size > 0){//不太懂这个操作???????
do{}while(index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext(){
return next != null;
}
final Node<K,V> nextNode(){
Node<K,V>[] t;
Node<K,V> e = next;
if(modCount != expectedModCount)
throw new ConcurrentModificationException();
if(e == null)
throw new NoSuchElementException();
if((next = (current = e).next) == null && (t = table) != null){
do{}while(index < t.length && (next = t[index++]) == null);
}
return e;
}
public final void remove(){
Node<K,V> p = current;
if(p == null)
throw new IllegalStateException();
if(modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key),key,null,false,false);
expectedModCount = modCount;
}
}
final class KeyIterator extends HashIterator implements Iterator<K>{
public final K next(){
return nextNode().key;
}
}
final class ValueIterator extends HashIterator implements Iterator<V>{
public final V next(){
return nextNode().value;
}
}
final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>>{
public final Map.Entry<K,V> next(){
return nextNode();
}
}
static class HashMapSpliterator<K,V>{
//静态内部类,HashMap分割器
final HashMap<K,V> map;
Node<K,V> current;
int index;
int fence;
int est;
int expectedModCount;
HashMapSpliterator(HashMap<K,V> m, int origin, int fence, int est, int expectedModCount)
{
this.map = m;
this.index = origin;
this.fence = fence;
this.est = est;
this.expectedModCount = expectedModCount;
}
final int getFence(){
int hi;
if((hi = fence) < 0){
HashMap<K,V> m = map;
est = m.size;
expectedModCount = m.modCount;
Node<K,V>[] tab = m.table;
hi = fence = (tab == null)
? 0
: tab.length;
}
return hi;
}
public final long estimateSize(){
getFence();
return (long) est;
}
}
static final class KeySpliterator<K,V> extends HashMapSpliterator<K,V> implements Spliterator<K>{
//键迭代器
KeySpliterator(HashMap<K,V> m, int origin, int fence, int est, int expectedModCount){
super(m,origin,fence,est,expectedModCount);
}
public KeySpliterator<K,V> trySplit(){
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid || current != null)
? null
: new KeySpliterator<>(map,lo,index = mid, est >>>= 1,expectedModCount);
}
public void forEachRemaining(Consumer<? super K> action){
int i, hi, mc;
if(action == null)
throw new NullPointerException();
HashMap<K,V> m = map;
Node<K,V>[] tab = m.table;
if((hi = fence) < 0){
mc = expectedModCount = m.modCount;
hi = fence = (tab == null) ? 0 : tab.length;
}
else
mc = expectedModCount;
if(tab != null && tab.length >= hi && (i = index) >= 0 && (i < (index = hi) || current != null)){
Node<K,V> p = current;
current = null;
do{
if(p == null)
p = tab[i++];
else{
action.accept(p.key);
p = p.next;
}
}while(p != null || i < hi);
if(m.modCount != mc)
throw new ConcurrentModificationException();
}
}
public boolean tryAdvance(Consumer<? super K> action){
int hi;
if(action == null)
throw new NullPointerException();
Node<K,V>[] tab = map.table;
if(tab != null && tab.length >= (hi = getFence()) && index >= 0){
while(current != null || index < hi){
if(current == null)
current = tab[index++];
else{
K k = current.key;
current = current.next;
action.accept(k);
if(map.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
}
}
return false;
}
public int characteristics(){
return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) | Spliterator.DISTINCT;
}
}
static final class ValueSpliterator<K,V> extends HashMapSpliterator<K,V> implements Spliterator<V>{
//值迭代器
ValueSpliterator(HashMap<K,V> m, int origin, int fence, int est, int expectedModCount){
super(m,origin,fence,est,expectedModCount);
}
public ValueSpliterator<K,V> trySplit(){
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid || current != null)
? null
: new ValueSpliterator<>(map,lo,index = mid, est >>>= 1,expectedModCount);
}
public void forEachRemaining(Consumer<? super V> action){
int i,hi,mc;
if(action == null)
throw new NullPointerException();
HashMap<K,V> m = map;
Node<K,V>[] tab = m.table;
if((hi = fence) < 0){
mc = expectedModCount = m.modCount;
hi = fence = (tab == null)
? 0
: tab.length;
}
else
mc = expectedModCount;
if(tab != null && tab.length >= hi && (i = index) >= 0 && (i < (index = hi) || current != null)){
Node<K,V> p = current;
current = null;
do{
if(p == null)
p = tab[i++];
else{
action.accept(p.value);
p = p.next;
}
}while(p != null || i < hi);
if(m.modCount != mc)
throw new ConcurrentModificationException();
}
}
public boolean tryAdvance(Consumer<? super V> action){
int hi;
if(action == null)
throw new NullPointerException();
Node<K,V>[] tab = map.table;
if(tab != null && tab.length >= (hi = getFence()) && index >= 0){
while(current != null || index < hi){
if(current == null)
current = tab[index++];
else{
V v = current.value;
current = current.next;
action.accept(v);
if(map.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
}
}
return false;
}
public int characteristics(){
return (fence < 0 || est == map.size ? Spliterator.SIZED : 0);
}
}
static final class EntrySpliterator<K,V> extends HashMapSpliterator<K,V> implements Spliterator<Map.Entry<K,V>>{
//键值对分割器
EntrySpliterator(HashMap<K,V> m, int origin, int fence, int est, int expectedModCount){
super(m,origin,fence,est,expectedModCount);
}
public EntrySpliterator<K,V> trySplit(){
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return(lo >= mid || current != null)
? null
: new EntrySpliterator<>(map,lo,index = mid, est >>>= 1, expectedModCount);
}
public void forEachRemaining(Consumer<? super Map.Entry<K,V>>action){
int i,hi,mc;
if(action == null)
throw new NullPointerException();
HashMap<K,V> m = map;
Node<K,V>[] tab = m.table;
if((hi = fence) < 0){
mc = expectedModCount = m.modCount;
hi = fence = (tab == null) ? 0 : tab.length;
}
else
mc = expectedModCount;
if(tab != null && tab,length >= hi && (i = index) >= 0 && (i < (index = hi) || current != null)){
Node<K,V> p = current;
current = null;
do{
if(p == null)
p = tab[i++];
else{
action.accept(p);
p = p.next;
}
}while(p != null || i < hi);
if(m.modCount != mc)
throw new ConcurrentModificationException();
}
}
public boolean tryAdvance(Consumer<? super Map.Entry<K,V>> action){
int hi;
if(action == null)
throw new NullPointerException();
Node<K,V>[] tab = map.table;
if(tab != null && tab.length >= (hi = getFence()) && index >= 0){
while(current != null || index <hi){
if(current == null)
current = tab[index++];
else{
Node<K,V> e = current;
current = current.next;
action.accept(e);
if(map.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
}
}
return false;
}
public int characteristics(){
return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) | Spliterator.DISTINCT;
}
}
/**
*接下来就是最难的地方了,红黑树
**/
Node<K,V> newNode(int hash,K key, V value, Node<K,V> next){
//新建一个结点,返回新建的结点
return new Node<>(hash, key, value, next);
}
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next){
//将TreeNode替换成普通结点,即将红黑树拆成链表,返回一个普通结点
return new Node<>(p.hash, p.key, p.value, next)
}
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next){
//新建一个红黑树的结点,返回一个TreeNode
return new TreeNode<>(hash, key, value, next);
}
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next){
//将普通结点替换成TreeNode,即将链表建成红黑树,返回一个树结点
return new TreeNode<>(p.hash, p.key, p.value, next);
}
void reinitialize(){
//重新初始化
table = null;
entrySet = null;
keySet = null;
values = null;
modCount = 0;
threshold = 0;
size = 0;
}
//这些方法是为LinkedHashMap留的后路,在LinkedHashMap中才会实现,HashMap没实现
void afterNodeAccess(Node<K,V> p){}
void afterNodeInsertion(boolean evict){}
void afterNodeRemoval(Node<K,V> p){}
void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException{
//被writeObjct调用,确认兼容顺序,emmmmm....不太懂???
Node<K,V>[] tab;
if(size > 0 && (tab = table) != null){
for(int i = 0; i < tab.length; ++i){
for(Node<K,V> e = tab[i]; e != null; e = e.next){
s.writeObject(e.key);
s.writeObject(e.value);
}
}
}
}
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>{
//静态内部类,继承的是LinkedHashMap中的Entry类
TreeNode<K,V> parent;//指向父节点
TreeNode<K,V> left;//指向左孩子
TreeNode<K,V> right;//指向右孩子
TreeNode<K,V> prev;//指向前置结点
boolean red;//设置是红结点还是黑结点
/*
此为继承下来的成员变量:
final int hash;
final K key;
V value;
Node<K,V> next;
*/
//从此处可以看出,TreeNode节点类应该是融合了二叉树结点和双向链表结点的特点
//总共有五个指针,分别指向前置结点,后置结点,父节点,左孩子结点和右孩子结点
//主要是可以方便双向链表和红黑树相互之间的转换
TreeNode(int hash, K key, V val, Node<K,V> next){
//构造器,传入哈希值,键和值以及下一个结点就可以构造一个新结点
super(hash, key, val, next);
}
final TreeNode<K,V> root(){
//返回红黑树的根节点
for(TreeNode<K,V> r = this, p; ;){
if((p = r.parent) == null)
return r;
r = p;
}
}
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];//将数组槽内的结点取出
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)//如果根不是链表中的头结点
rp.next = rn;//则将根的前置结点的后置指针指向根的前置结点,这两步就是将根节点从链表中删除
if(first != null)//如果数组槽内的原结点不是空
first.prev = root;//则将原结点的前置指针指向根结点
root.next = first;//再将根结点的后置指针指向数组槽内的原结点
root.prev = null;//根节点的前置指针指向空
}
//上面的操作简单来说就是将红黑树中的根节点从双向链表中取出作为双向链表的头结点并放入数组槽内
assert checkInvariants(root);
//防御性编程,检查是否满足红黑树和双向链表的条件
}
}
final TreeNode<K,V> find(int h, Object k, class<?> kc){
//在红黑树中寻找某个结点
TreeNode<K,V> p = this;
//本方法应该是TreeNode节点自己调用的,所以this为调用此方法的结点
//将该结点作为根节点来寻找某一个符合条件的结点
do{
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q;
if((ph = p.hash) > h)//如果结点的hash值大于传进来的hash值,则向左子树查
p = pl;
else if(ph < h)//如果结点的hash值小于传进来的hash值,则向右子树查
p = pr;
else if((pk == p.key) == k || (k != null && k.equals(pk)))
//如果相等,则查看key值是否相等,相等则直接返回该结点
return p;
//以上就是判断hash值是否相等,当hash值相等之后,再来下面判断key是否相等
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)
//否则说明左右孩子都不为空,则使用compareComparables来比较左右结点的key值
p = (dir < 0) ? pl : pr;
//否则比较的值小于0,下一次循环进入左孩子,大于0,下一次循环进入右孩子
else if((q = pr.find(h, k, kc)) != null)
//如果上面的判断还是无法判断,则递归查找右孩子,找到了则返回
return q;
else
//如果右孩子递归也没有找到,则开始找左孩子
p = pl;
}while(p != null);
return null;//如果还是没有找到,则返回空值
}
final TreeNode<K,V> getTreeNode(int h, Object k){
//获得树中的结点,首先判断调用的是否为根结点,不是则找到根节点
//再调用find方法寻找
return ((parent != null) ? root() : this).find(h, k, null);
}
static int tieBreakOrder(Object a, Object b){
//当哈希值相等并且key值也相等,则比较两者的内存地址的哈希码
int d;
if(a == null || b == null || (d = a.getClass().getName().compareTo(b.getClass().getName())) == 0)
//如果两者都不为空且两者都是同一个类,则比较两者在内存地址的哈希码,identityHashCode方法即获得对象在内存地址的哈希码
d = (System.identityHashCode(a) <= System.identityHashCode(b) ? -1 : 1);
return d;
}
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.left = x.right = null;//先将当前结点的左孩子和右孩子设置为空
if(root == null){//如果根为空,说明刚开始建树
x.parent = null;//将x节点的父节点设置为空,说明x节点为根结点
x.red = false;//x节点颜色设置为黑(符合红黑树的第二特征:根必须是黑色结点)
root = x;//将x节点设置为根结点
}else {
K k = x.key;//否则,说明根不是空,则相当于向红黑树中插入结点,获得当前结点的key值
int h = x.hash;//获得当前结点的hash值
Class<?> kc = null;//设置一个Class类,用于后面的比较器
for(TreeNode<K,V> p = root; ;){//对红黑树进行遍历
int dir, ph;//dir作为标记,指示结点向什么地方插入,ph为当前结点的哈希值
K pk = p.key;//获得当前结点的key值
if((ph = p.hash) > h)//新插入的结点和当前结点比较hash值,大的向右,小的向左
dir = -1;
else if(ph < h)
dir = 1;
else if((kc == null) && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc,k,pk)) == 0)
//如果hash值相等,则比较key值,如果key值也相等,则比较内存地址的hash值,此处两行代码实际上比较了两种情况
//注意if条件中的还有一个对dir中的比较
dir = tieBreakOrder(k,pk);
TreeNode<K,V> xp = p;//先将p存储到xp,方便后面插入
if((p = (dir <= 0) ? p.left : p.right) == null){
//如果dir小于等于0,则p移向p的左孩子,如果dir大于0,则p移向p的右孩子
//知道p等于空时,才进行插入操作
x.parent = xp;//此时p所指的位置即x所要插入的位置,xp所指的结点即p的父节点.因此将x的父节点设置为xp
if(dir <= 0)
xp.left = x;//如果dir小于等于0,则将x设置为xp的左孩子
else
xp.right = x;//如果dir大于0,则将x设置为xp的右孩子
root = balanceInsertion(root, x);//将插入结点后的红黑树调整,并返回根结点
break;//结束红黑树的循环,继续双向链表的循环
}
}
}
}
moveRootToFront(tab,root);
//链表循环结束,说明链表结点全都转换进入红黑树中,再将红黑树的根节点设置为链表首节点并且放入哈希表中
}
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,也就是新建一个Node结点,将必要的信息放进去再返回新结点即可
if(tl == null)//如果当前结点为空,说明链表为空,设置头结点
hd = p;
else
tl.next = p;//否则当前结点的后置指针指向新节点
tl = p;//将新结点设置为当前结点
}
return hd;//返回头结点
}
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab, int h, K k, V v){
//将结点插入到红黑树中,即先找到插入点,再创建新结点,插入进去,最后调整红黑树,将
//红黑树的根节点作为链表首节点并且放入哈希表槽内
Class<?> kc = null;//Class类,用作compareable比较器
boolean searched = false;//查询标记
TreeNode<K,V> root = (parent != null) ? root() : this;//首先找到根节点
for(TreeNode<K,V> p = root; ;){//从根结点开始查找,找到适合插入的地方再进行插入操作
int dir, ph; K pk;//dir为指示标,每一次循环根据此指示标进行向左深入还是向右深入
//ph为当前结点的哈希值,pk为当前结点的key值
if((ph = p.hash) > h)//首先比较hash值,大则dir等于-1,指示下一次向左子树遍历,大于
//则dir等于1,指示下一次向右子树遍历
dir = -1;
else if(ph < h)
dir = 1;
else if((pk = p.key) == k || (k != null && k.equals(pk)))//如果hash值相等,则比较key值,如果相等,说明红黑树中
//已有此节点,不能插入,返回当前结点
return p;
else if((kc == null) && (kc = compareClassFor(k)) == null || (dir = compareComparables(kc,k,pk)) == 0){
//否则即hash值相等,但是key值不等,故先比较key值,如果小于则dir等于-1,大于则dir等于1,相等则进入if语句
if(!searched){//此处search表示先向下寻找有没有同样的结点,有则直接返回该结点,不用插入了,没有才继续进行
//而且每次插入操作只做一次此操作,这样的好处是防止向下寻找的时候忽然找到一个一样的结点,浪费资源。所以在第一次
//寻找的时候就先确定该结点是否不存在树中
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);//如果key值也一样,则比较内存地址中的hash值
}
TreeNode<K,V> xp = p;//先记录p结点
if((p = (dir <= 0) ? p.left : p.right) == null){
//根据dir选择进入左孩子还是右孩子,再判断是否为空,不为空继续循环,为空开始插入
Node<K,V> xpn = xp.next;//记录结点的下一个结点
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);//创建一个新的树结点
if(dir <= 0)//根据dir判断新结点应该插入左边还是右边
xp.left = x;
else
xp.right = x;
xp.next = x;//将xp结点的后置指针指向新节点x
x.parent = x.prev = xp;//将新结点的父节点和前置指针都设置为xp节点
if(xpn != null)//如果xp不是尾结点
((TreeNode<K,V>)xpn).prev = x;//则设置xp的后一个结点的前置指针指向x新节点
moveRootToFront(tab,balanceInsertion(root,x));//先调整,再将根结点作为链表的首节点并放入哈希槽中
return null;//插入成功,则返回null
}
}
}
final void removeTreeNode(HashMap<K,V> map,Node<K,V>[] tab, boolean movable){
//删除一个结点,首先在链表中删除,然后在红黑树中删除,链表中删除很好删,主要是红黑树中如何删
//首先判断被删除的结点是否有左右孩子,没有直接删除就够了,有一个孩子也好办,用那个孩子来顶替
//它就够了,最难的是有两个孩子,此处给的处理方法是寻找中序遍历中的下一个结点,将要删除的结点
//和该结点替换,因为它的有两个孩子的话,那么它中序遍历的下一个结点最多有一个孩子,替换了之后
//就变成了上面几种情况之一了。
int n;
if(tab == null || (n = tab.length) == 0)//首先判断哈希表是否为空,空则删除不了,直接返回
return;
int index = (n - 1) & hash;//否则就先获得要删除结点在哈希表中的位置,因为是被删除结点引用
//的这个方法,所以hash就是该结点的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 || root.right == null || (rl = root.left) == null || rl.left == null){
//如果根结点为空或者根结点的左孩子为空或者根节点的右孩子为空或者根结点的左孩子的左孩子为空
//说白了就是所有结点的数量加起来不足八个,那就拆红黑树,变链表并返回
tab[index] = first.untreeify(map);
return;
}
//上面基本是在链表与红黑树之间进行操作,现在开始是在红黑树上面进行操作了
TreeNode<K,V> p = this, pl = left, pr = right, replacement;
//p为要删除的结点,pl为要删除的结点的左孩子,pr为要删除的结点的右孩子,replacement为替代结点
//这里要明白一个概念:在链表中删除不代表在红黑树中删除,这是两个体系,因为每一个TreeNode都有
//五个指针,即指向父节点,指向左孩子,指向右孩子,指向前置结点,指向后置结点
if(pl != null && pr != null){
//如果要删除的结点的左孩子和右孩子都不为空,即为最难搞定的一种,开始找替换的结点
TreeNode<K,V> s = pr, sl;
//首先设置s为p的右孩子,sl为s的左孩子
while((sl = s.left) != null)
//首先s是p的右孩子,然后一直向s的左边往下寻找,一直到s的左孩子
//是空的时候取s节点。想象一下,这个s的值是大于p并且最接近p的那
//一个结点,也就是中序遍历的p的下一个结点
s = sl;
boolean c = s.red; s.red = p.red; p.red = c;
//将p结点与s的结点颜色互换
TreeNode<K,V> sr = s.right;
//取s结点的右孩子sr
TreeNode<K,V> pp = p.parent;
//取p结点的父节点pp
if(s == pr){
//如果s就是p的右孩子,也就是说s和p是直接相关联的
p.parent = s;
//那就将s作为p的父节点
s.right = p;
//将p作为s的右结点
}else {
//否则说明s与p是隔开的
TreeNode<K,V> sp = s.parent;
//取s的父节点sp
if((p.parent = sp) != null){
//将sp作为p的父节点
if(s == sp.left)
//如果s是sp的左结点
sp.left = p;
//则将p也设置为sp的左结点
else
sp.right = p;
//否则就将p设置为sp的右结点
}
if((s.right = pr) != null)
//将p的右结点设置为s的右结点
pr.parent = s;
//将s设置为pr的父结点
}
//以上就是把s和p的基本定位定好,如果s和p相邻,直接替换
//如果不相邻,则将s的父结点作为p的父结点,将p的右孩子结点
//作为s的右孩子结点,只做了两步,下面就是其它几步
p.left = null;
//先将p的左孩子设置为空,因为原s的左孩子必为空
if((p.right = sr) != null)
//将s的右孩子设置为p的右孩子,如果s没有右孩子就不做处理
sr.parent = p;
//将p设置为s右孩子的父结点
if((s.left = pl) != null)
//将p的左孩子设置为s的左孩子,如果p没有左孩子就不做处理
pl.parent = s;
//将s设置为p的左孩子的父结点
if((s.parent = pp) == null)
//将p的父结点设置为s的父结点,如果p的父结点为空,则说明p是根
root = s;
//则将s作为根结点
else if(p == pp.left)
//否则判断p是左孩子还是右孩子
pp.left = s;
//如果是左孩子,就将s作为左孩子
else
pp.right = s;
//否则就将s作为右孩子
//此上就将p和s完全替换完全了,接下来寻找替换p的结点
if(sr != null)
//如果s的右孩子不为空
replacement = sr;
//则将其作为替换结点
else
replacement = p;
//否则说明s既没有左孩子也没有右孩子,则将p自己作为替换元素
}
//此上是在要删除的结点左右孩子都有的情况下进行的操作,如果不符合
//则说明要删除的结点最多有一个孩子
else if(pl != null)
//如果p结点不是两个孩子都有,则判断它是否有左孩子
replacement = pl;
//如果有左孩子,则将其作为替换结点
else if(pr != null)
//否则判断它是否有右孩子
replacement = pr;
//如果有右孩子,则将其作为替换结点
else
replacement = p;
//否则说明p两个孩子都没有,则将其自己作为替换元素
if(replacement != p){
//先判断替换结点是否为它自己,如果不是
TreeNode<K,V> pp = replacement.parent = p.parent;
//取现在的p结点的父结点(注意是现在,如果p和s替换了,则相当于取s的原本的父结点)
//并将pp设置为替换结点的父结点
if(pp == null)
root = replacement;//如果父结点为空,则将替换结点作为根
else if(p == pp.left)//否则判断p是左孩子还是右孩子
pp.left = replacement;
//如果p是左孩子,则将替换结点作为左孩子
else
pp.right = replacement;
//否则将替换结点作为右孩子
p.left = p.right = p.parent = null;
//将p的左孩子指针、右孩子指针和父结点指针都置为空
}
TreeNode<K,V> r = p.red ? root : blanceDeletion(root,replacement);
//如果p为红色,则不需要调整,如果为黑色,则需要进行调整
if(replacement == p){
//如果p自己就是替换元素,说明p既没有左孩子也没有右孩子
TreeNode<K,V> pp = p.parent;
//取p的父结点
p.parent = null;
//将p的父指针置为空
if(pp != null){
//如果父结点不为空
if(p == pp.left)
//如果p是左孩子
pp.left = null;
//则将pp的左孩子指针置为空
else if(p == pp.right)
pp.right = null;
//否则将pp的右孩子指针置为空
}
}
if(movable)//如果是可调整的,则将根调整到链表的首节点,放入哈希表中
moveRootToFront(tab,r);
}
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit){
//扩容方法,index表示扩容前哈希表的索引,bit表示扩容前哈希表的容量
//当原数组扩容时发现数组索引处放的是红黑树根节点的时候调用此方法
TreeNode<K,V> b = this;//b为调用此方法的结点
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;
if((e.hash & bit) == 0){
//如果结点的哈希值和新容量相与为0,表示该结点扩容后也在这个位置
if((e.prev = loTail) == null)
//将e的前置结点设置为尾结点,如果为空
loHead = e;
//则将e作为首结点
else
//否则将尾结点的后置结点设置为e
loTail.next = e;
loTail = e;
//再将尾结点移动到e结点位置,从而完成了增加一个结点的任务
++lc;
//第一个链表结点个数加一
}
else {
//如果不为0,由于哈希表扩容为原来的两倍,所以必在另外一个槽内
if((e.prev = hiTail) == null)
//同样,将e的前置结点设置为尾结点,如果尾结点为空
hiHead = e;
//将e设置为头结点
else
hiTail.next = e;
//否则尾结点的后置结点为e
hiTail = e;
//尾结点移动到e处
++hc;
//第二个链表结点个数加一
}
}
//将红黑树拆分成两个链表成功
if(loHead != null){
//如果第一个链表的头结点不为空,说明链表中有结点
if(lc <= untreeify_threshold)
//如果链表内结点个数小于建树阈值
tab[index] = loHead.untreeify(map);
//则将红黑树拆成链表并将表头放入哈希表槽内
else{
//否则,说明表内结点个数大于建树阈值
tab[index] = loHead;
//先将表头放入哈希表槽内
if(hiHead != null)
//如果另外一个表为空,说明结点都在这个表内了,不需要再建树
//本来就是树,如果另外一个表不为空,说明树结构破坏了,所以
//重新建树
loHead.treeif(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);
//否则,重新建树
}
}
}
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p){
//左旋操作,左旋操作的具体内容是将p结点向左下方旋转,把p的右孩子r向左上方
//旋转,代替p结点,p结点则作为r的左孩子,r继承p结点的父结点,且r结点如果有
//左孩子,则将其拆下来作为p结点的右孩子
TreeNode<K,V> r, pp, rl;
//传入参数为p,此操作是以p结点为旋点向左旋转,r为p的右右孩子,pp为p结点
//的父结点,rl为r的左孩子
if(p != null && (r = p.right) != null){
//首先判断两个结点是否为空,如果为空就转不了了
if((rl = p.right = r.left) != null)
//再将r的左孩子作为p的右孩子,如果r没有左孩子,则不做操作
rl.parent = p;
//否则再将p设置为r的左孩子的父结点
if((pp = r.parent = p.parent) == null)
//再将p的父结点作为r的父结点,如果p没有父结点,则说明p为根结点
(root = r).red = false;
//则将r作为根节点且将其颜色染黑
else if(pp.left == p)
//否则判断p是左孩子还是右孩子
pp.left = r;
//如果是左孩子,则将r作为pp的左孩子
else
pp.right = r;
//否则将r作为pp的右孩子
r.left = p;
//再将p作为r的左孩子
p.parent = r;
//将r作为p的父结点
}
return root;
//调整完后返回根结点
}
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p){
//右旋操作,与左旋操作相反,将p结点向右下方向右旋转,将p的左孩子l向右上方
//向右旋转,代替p结点,p结点作为l结点的右结点,l作p结点的父结点,且l结点
//如果有右孩子,则将其拆下来作为p结点的左孩子
TreeNode<K,V> l, pp, lr;
//l为p结点的左孩子,pp为p结点的父结点,lr为l的右孩子
if(p != null && (l = p.left) != null){
//如果p不为空且p的左孩子不为空,开始右旋
if((lr = p.left = l.right) != null)
//如果l的右孩子不为空,则将其作为p的左孩子
lr.parent = p;
//将p作为lr的父结点
if((pp = l.parent = p.parent) == null)
//将p的父结点作为l的父结点,如果为空
(root = l).red = false;
//则将l作为根节点,并且将其染为黑色
else if(pp.right == p)
//判断p为左孩子还是右孩子
pp.right = l;
//如果为右孩子,则将l作为pp的右孩子
else
pp.left = l;
//否则将l作为左孩子
l.right = p;
//将p设置为l的右孩子
p.parent = l;
//将l设置为p的父结点
}
return root;
//返回根结点
}
static <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;;){
//开始进行循环向上调整,xp为x的父结点,xpp为x的祖父结点
//xppl为xpp的左孩子,xppr为xpp的右孩子
if((xp = x.parent) == null){
//如果x的父结点为空,则说明x结点是根结点
x.red = false;
//将x的颜色染黑
return x;
//返回根结点x
}else if(!xp.red || (xpp = xp.parent) == null)
//如果x的父结点为黑色或者x的祖父结点不存在,即x的父结点为根
return root;
//不需做任何处理,直接返回根
//上面是对x父结点为黑色进行讨论,下面则默认x的父结点为红色
if(xp == (xppl = xpp.left)){
//如果xp为xpp的左孩子
if((xppr = xpp.right) != null && xppr.red){
//如果xpp的右孩子不为空且是红色的
xppr.red = false;
//将xpp的右孩子染成黑色
xp.red = false;
//将xp染成黑色
xpp.red = true;
//将xpp染成红色
x = xpp;
//将x移动到xpp位置,向上调整
}else{
//否则,说明xpp的右孩子为黑色
if(x == xp.right){
//如果新插入的结点是右孩子
root = rotateLeft(root, x = xp);
//则以x的父结点为旋点进行左旋转
xpp = (xp = x.parent) == null ? null : xp.parent;
//旋转后,x位于原来的xp的位置,xp转到了原来的x的位置,
//因此需要重新调整,调整后,xp变为x的父结点,xpp还是原来的位置
//也就是说只有x和xp互换了位置,但是xp还是x的父结点
}
if(xp != null){
//无论第一次插在左孩子的位置上还是插在右孩子的位置上,
//都要进行进行右旋转,区别是如果插在左孩子位置上
//只要进行一次右旋转就行,而插在右孩子位置上,则先
//进行左旋转,再进行右旋转,因此只需要判断xp是否为空就够了
xp.red = false;
//先将xp染黑
if(xpp != null){
xpp.red = true;
//再将xpp染红
root = rotateRight(root,xpp);
//以xpp为旋点进行右旋转
}
}
}
}else {
//否则,xp为xpp的右孩子,步骤基本一致
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);
}
}
}
}
}
}
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, TreeNode<K,V> x){
//删除之后对红黑树进行调整,这个x有两种可能,如果删除的结点没有孩子,那么这个x
//就是将要被删除的结点,但是还没有删除,如果删除的结点有孩子,那么这个孩子一定
//是红色的,因为它只能有一个孩子,如果是黑色的,那么树就不平衡了。所以当传入的
//是被删除结点的孩子的话,直接将它转成黑色就结束了。
for(TreeNode<K,V> xp, xpl, xpr;;){
//循环调整,xp是x的父结点,xpl是xp的左孩子,xpr是xp的右孩子
if(x == null || x == root)
//如果x是空值或者x是根,直接返回
return root;
else if((xp = x.parent) == null){
//如果xp是空值,说明x是根,将x染黑并返回
x.red = false;
return x;
}
else if(x.red){
//如果x是将要删除的结点,那么它一定不是红色,如果是红色
//那么一定是要删除的结点的孩子,而且只能是红色,将其染成黑色
//整棵树就平衡了。
x.red = false;
return root;
}
//如果第一轮到这里还没有结束,那么说明被删除的结点没有孩子,传进来的是他自己
else if((xpl = xp.left) == x){
//如果x是左孩子
if((xpr = xp.right) != null && xpr.red){
//首先判断x的兄弟结点,即xp的右孩子是否为红色,如果为红色,则为第二种情况
xpr.red = false;
//如果是则先将xpr染黑
xp.red = true;
//再将xp染红
root = rotateLeft(root,xp);
//以xp为旋点向左旋转
xpr = (xp = x.parent) == null ? null : xp.right;
//旋转完再恢复结构,令xp为x的父结点,xpr为xp的右孩子
}
//如果上面的if语句没有被处理,则说明xpr为黑色结点,xpr不会为空,如果为空则树开始就不平衡
if(xpr == null)
//如果xp的右结点为空,则直接将x移到xp位置,结束此次循环
//这是属于第二种情况,删除结点的兄弟结点是红色的,且其孩子
//都是黑色的(叶子结点都是黑色的)
x = xp;
//下一次循环开始时判断x为红色,转成黑色,这样这颗以原来xpr
//左转上去为根的左子树增加了一个黑色结点(xp转下来变成黑色)
//右子树没增加也没减少黑色结点(原来的xpr为红色转上去染黑)
//而且这颗树的整体黑色结点也没有变化
else{
//1.运行到此处,有一种情况是前面两个if语句都运行了,则说明被删除结
//点的兄弟结点是红色的,那么它的子结点只能是两个黑色结点,而进行左转后
//xp的右孩子则是原来的xpr的左孩子,也一定是黑色的且没有左右孩子.
//2.运行到此处,还有一种可能是前面两个if语句都没有运行,则说明被删除结点
//的兄弟结点是黑色的,那么开始来判断它的子结点是什么情况的了
TreeNode<K,V> sl = xpr.left, sr = xpr.right;
//sl为xpr的左孩子,sr为xpr的右孩子
if((sr == null || !sr.red) && (sl == null || !sr.red)){
//1.如果xpr没有左右孩子,则将xpr染成红色,那么xp为根节点的子树达到
//平衡了(左子树少一个黑色结点,为空,右子树只有一个红色结点)
//2.如果xpr的两个孩子都是黑色的,那么只需要将它本身染红,就可以达到
//子树的平衡了(左子树少了一个黑色结点,右子树一个黑色结点变成了红色)
//这是第三种情况
xpr.red = true;
x = xp;
//1.再将x移动到xp位置上,第二轮循环将其染黑,保证树的平衡,返回结束
//2.此时将x移动到xp位置,如果xp是红色的,那直接染黑就可以保证整棵树的
//平衡,如果是黑色的,那继续向上调整。
}else{
//否则说明xpr的孩子中必有红色结点,运行到此时,说明是属于2这个情况
if(sr == null || !sr.red){
//先判断xpr的左孩子是否为红,如果右孩子为黑则左孩子必为红
//而左孩子为红说明是属于第四种情况
if(sl != null)
//如果sl不为空,则将sl先染黑
sl.red = false;
xpr.red = true;
//再将xpr染红
root = rotateRight(root, xpr);
//以xpr为旋点向右旋转
xpr = (xp = x.parent) == null ? null : xp.right;
//重新调整结构,使xp为x的父结点,xpr为xp的右孩子
}
//不管xpr的孩子中谁为红,都必须做以下操作,只是左孩子为红的话需要先
//进行右旋转再做以下操作
//如果上一个if语句处理了,那么说明为第四种情况
//如果上一个if语句没有处理,则说明为第五种情况
if(xpr != null){
//先判断xpr是否为空
xpr.red = (xp == null) ? false : xp.red;
//先将xpr设置成xp一样的颜色,防止整棵树的黑色结点有变化
if((sr = xpr.right) != null)
//如果xpr的右孩子不为空
sr.red = false;
//将sr染为黑色,代替xpr作为右子树的黑色结点
}
if(xp != null){
//如果xp不为空
xp.red = false;
//将xp染为黑色并向左下旋转,代替被删除的结点当黑色结点
root = rotateLeft(root, xp);
//以xp为旋点向左旋转
}
x = root;
//局部调整完毕,可以直接返回
}
}
}else{
//否则,说明x是xp的右孩子,镜像调整,基本类似
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
}
}
}
}
}
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;
//tp为t的父结点,tl为t的左孩子,tr为t的右孩子,tb为t的前置结点,tn为t的后置结点
if(tb != null && tb.next != t)
//检查t的前置结点的后置指针是否指向自己
return false;
if(tn != null && tn.prev != t)
//检查t的后置结点的前置指针是否指向自己
return false;
if(tp != null && t != tp.left && t != tp.right)
//检查t是否为自己父结点的左孩子或者右孩子
return false;
if(tl != null && (tl.parent != t || tl.hash > t.hash))
//检查t是否是自己左孩子的父结点且自己左孩子的hash值是否比自己小
return false;
if(tr != null && (tr.parent != t || tr.hash < t.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;
}
}
}
参考资料:
http://www.it610.com/article/3982640.htm
https://blog.youkuaiyun.com/qq_39470742/article/details/83895633
https://blog.youkuaiyun.com/xiehuanhuan1991/article/details/79615960
https://blog.youkuaiyun.com/u011328417/article/details/80728571
https://blog.youkuaiyun.com/qpzkobe/article/details/79533237
https://www.cnblogs.com/loading4/p/6239441.html
http://www.importnew.com/31096.html
https://blog.youkuaiyun.com/u011392897/article/details/60141739
https://yq.aliyun.com/articles/454775
https://blog.youkuaiyun.com/bnmb888/article/details/77164485