集合底层源码分析之HashMap《上》(三)
前言
HashMap继承于AbstractMap,实现了Map, Cloneable,Serializable接口。HashMap 允许key、value为null,不保证插入顺序。
源码分析
建议本内容从头到尾看,先看源码再看流程图,否则你会看的很懵逼。
HashMap主要属性及构造方法分析
主要介绍HashMap主要属性及hash值的计算方法、构造函数等
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V> , Cloneable, Serializable{
//默认初始化容器大小:1<<4=00001=10000=16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容器大小:1<<30=1073741824
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认加载因子:0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//链表转化成红黑树的阈值:8
static final int TREEIFY_THRESHOLD = 8;
//红黑树转化成链表的阈值:6
static final int UNTREEIFY_THRESHOLD = 6;
//红黑树最小容器大小:64
static final int MIN_TREEIFY_CAPACITY = 64;
//节点数组
transient Node<K, V>[] table;
//HashMap映射的Set视图,可用于遍历操作
transient Set<Map.Entry<K,V>> entrySet;
//HashMap元素大小
transient int size;
//操作次数
transient int modCount;
//实际扩容阈值:阈值= 容器大小 * 加载因子
int threshold;
//实际加载因子
final float loadFactor;
//计算hash值
static final int hash(Object key) {
int h;
//允许key值为空,返回0
//计算hash值(h = key.hashCode()) ^ (h >>> 16)
//比如 key=abc
//"abc".hashCode() = 96354 = 0000 0000 0000 0001 0111 1000 0110 0010
//^ 异或运算相同则0不同则1
//"abc".hashCode() >>> 16 = 1 = 0000 0000 0000 0000 0000 0000 0000 0001
//结果:96355 = 0000 0000 0000 0001 0111 1000 0110 0011
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//有参构造,传入指定容器容量及加载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: "+initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: "
+loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
//有参构造,传入指定容器容量,但使用默认加载因子
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//无参构造,使用默认加载因子及容器容量
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}
}
//有参构造,传入其它map对象,使用默认加载因子
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
}
tableSizeFor()方法源码分析
保证数组的长度永远是2次幂
//返回给定目标容量大小的2次方(通过位移和或运算计算出阈值,基本位移2位或4位时基本已经确定大小了)。
static final int tableSizeFor(int cap) {
//如:cap = 18
//n = 17 = 10001
int n = cap - 1;
//01000 | 10001 = 11001 = 25
n |= n >>> 1;
//00110 | 11001 = 11111 = 31
n |= n >>> 2;
//00001 | 11111 = 11111 = 31
n |= n >>> 4;
//0000 0000 | 0001 1111 = 0001 1111 = 31
n |= n >>> 8;
//0000 0000 0000 0000 | 0000 0000 0001 1111 = 0000 0000 0001 1111 = 31
n |= n >>> 16;
//如果n小于0返回1,否则大于最大容器容量返回最大容器容量,否则返回n+1
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
Node类源码分析
链表结构,HashMap的原始结构
/*
* node单向链表数据结构
*/
static class Node <K,V> implements Map.Entry <K,V>{
final int hash; //hash值
final K key; //键
V value; //值
Node<K, V> next;//下一个节点
/**
* 有参构造函数, 创建节点及关系
*
* @param hash
* @param key
* @param value
* @param next
*/
Node(int hash, K key, V value, Node<K, V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
/**
* 获取键
*
* @return
*/
public final K getKey() {
return key;
}
/*
* 获取值
*/
public final V getValue() {
return value;
}
/*
* 拼接键+值字符串
*/
public final String toString() {
return key + "=" + value;
}
/*
* hashCode值计算
*/
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
/*
* 将键对应值替换
*/
public final V setValue(V newValue) {
V oldValue = value;
value = newValue; //将当前的值替换为传进来的值
return oldValue; //返回之前的值
}
/**
* 比较两个对象是否相等
* @param o
* @return
*/
public final boolean equals(Object o) {
if (o == this)//如果当前对象相等
return true; //直接返回true
if (o instanceof Map.Entry) {//判断对象类型是不是Map.Entry
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;//强转为Map.Entry
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))//如果两个键及两个值相等返回true
return true;
}
return false;//返回false
}
}
TreeNode类源码分析
/**
* 创建树形节点(内容过多,省略部分代码,后续讲解)
*/
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; //父节点
TreeNode<K,V> left; //左子节点
TreeNode<K,V> right; //右子节点
TreeNode<K,V> prev; //需要删除的下一个节点
boolean red; //颜色:true红色 false黑色
/**
* 创建新节点.
*/
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;
}
}
}
rotateLeft()方法源码分析(左旋)
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p) {
TreeNode<K, V> r, pp, rl; //r=右子节点 pp=父节点 rl=右节点的左子节点
if (p != null && (r = p.right) != null) { //p节点不为空且p节点的右子节点不为空
if ((rl = p.right = r.left) != null) //r节点的左子节点赋值给p节点的右子节点且不为空
rl.parent = p; //将r节点的左子节点由r节点改为p节点
if ((pp = r.parent = p.parent) == null) //将p节点的父节点成为r节点的父节点且父节点为空
(root = r).red = false; //根节点改为r节点颜色为黑色
else if (pp.left == p) //如果p节点是父节点的左子节点
pp.left = r; //p父节点的左子节点改为r节点
else //如果p节点是父节点的右子节点
pp.right = r; //p父节点的右子节点改为r节点
r.left = p; //r节点的左子节点改为p节点
p.parent = r; //p节点的父节点改为r节点
}
return root; //返回根节点
}
案例讲解
继续讲解
rotateRight()方法源码分析(右旋)
static <K,V> TreeNode<K, V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p) {
TreeNode<K, V> l, pp, lr; //l=左子节点 pp=父节点 lr=左节点的右子节点
if (p != null && (l = p.left) != null) { //p节点不为空且p节点的左子节点不为空
if ((lr = p.left = l.right) != null) //l节点的右子节点赋值给p节点的左子节点且不为空
lr.parent = p; //将l节点的右子节点由l节点改为p节点
if ((pp = l.parent = p.parent) == null) //将p节点的父节点成为l节点的父节点且父节点为空
(root = l).red = false; //根节点改为l节点颜色为黑色
else if (pp.right == p) //如果p节点是父节点的右子节点
pp.right = l; //p父节点的右子节点改为l节点
else //如果p节点是父节点的左子节点
pp.left = l; //p父节点的左子节点改为l节点
l.right = p; //l节点的右子节点改为p节点
p.parent = l; //p节点的父节点改为l节点
}
return root; //返回根节点
}
案例讲解
//右旋
static <K, V> TreeNode<K, V> rotateRight(TreeNode<K, V> root, TreeNode<K, V> p) {
//l=p节点的左子节点,pp=p节点的父节点,lr=l节点的右子节点
TreeNode<K, V> l, pp, lr;
//判断p节点不等于null且l节点等于p节点的左子节点不等于null
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null) //判断l节点的右子节点等于p节点的左子节点赋值给lr节点不等null
lr.parent = p; //lr节点的父节点等于p节点
if ((pp = l.parent = p.parent) == null) //p节点的父节点等于l节点的父节点等于pp节点
(root = l).red = false; //root节点等于l节点,设为黑色
else if (pp.right == p) //pp节点的右子节点等于p节点
pp.right = l; //pp节点的右子节点等于l节点
else //否则pp节点的右子节点不是p节点
pp.left = l; //pp节点的左子节点等于l节点
l.right = p; //l节点的右子节点等于p节点
p.parent = l; //p节点的父节点等于l节点
}
return root; //返回root节点
}
继续讲解
左旋和右旋方法源码:小结
- 左旋和右旋本质上没有什么区别,无非就是右边的左旋(逆时针),左边的右旋(顺时针)。旋转分为三种情况:旋转节点的左(右)子节点不等于null、旋转节点的父节点等于null、旋转节点是父节点的左(右)子节点。
- 旋转节点的左(右)子节点不等于null。使父节点的左(右)子节点成为旋转节点的左(右)子节点,然后旋转节点的左(右)子节点指向父节点。建立父子关系
- 旋转节点的父节点等于null。使其旋转节点成为根节点且颜色为黑色
- 旋转节点是父节点的左(右)子节点。将父节点的左(右)子节点重新指向旋转节点的左(右)子节点
- 最后将旋转节点和左(右)子节点重新建立关系
treeifyBin()方法源码分析
//对于给定的哈希,替换bin中的所有链接节点,除非表太小,在这种情况下,调整大小。
final void treeifyBin(Node<K,V>[] tab, int hash) {
//n=数组长度,index=计算新元素的数组位置,e=数组位置节点
int n, index;Node<K,V> e;
//如果数组为null或者数组长度小于最小树形化容器64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); //调整大小
//否则通过hash值计算后下标数组不等于null(当前存放下标位置),重新建立连接
else if ((e = tab[index = (n - 1) & hash]) != null) {
//hd=头节点,t1=存放p节点,建立上下关系
TreeNode<K,V> hd = null, tl = null;
do {
//将节点变成树节点
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null) //如果t1等于null
hd = p; //头节点为数组的第一个节点
else { //否则
p.prev = tl; //p节点的上一个节点赋值为t1节点
tl.next = p; //t1的下一个节点赋值为p节点,建立上下关系
}
tl = p; //p节点赋值给t1节点
} while ((e = e.next) != null); //每次遍历e节点替换为下一个节点,直到等于null结束循环
if ((tab[index] = hd) != null) //将建立好关系的hd节点重新赋值给数组位置
hd.treeify(tab); //将数组进行树形化
}
}
resize()方法源码分析
/**
* 初始化大小,如果为空分配容器大小及阈值,否则以2的幂进行扩容
*/
final Node<K, V>[] resize() {
Node<K, V>[] oldTab = table; //原节点数组,第一次为null
int oldCap = (oldTab == null) ? 0 : oldTab.length; //原数组容器容量默认为0
int oldThr = threshold; //原阈值,默认0
int newCap, newThr = 0; //新容器、阈值为0
if (oldCap > 0) { //如果原容器容量大于0 (非第一次扩容)
if (oldCap >= MAXIMUM_CAPACITY) { //如果原容器容量大于最大值1的30次幂(10个亿左右)
threshold = Integer.MAX_VALUE; //阈值等于Integer最大值(20个亿左右)
return oldTab; //返回原数组
}
//新容器容量等于2倍原容器容量且小于最大容器容量且原容器容量大于默认容器值(16)
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; //新阈值等于原阈值的2倍
} else if (oldThr > 0) //原阈值大于0(初始化构造给定的阈值)
newCap = oldThr; //新容器容量等于传入的阈值
else { //原容器容量和原阈值都为0(无参构造,第一次进来分配默认大小)
newCap = DEFAULT_INITIAL_CAPACITY; //新容器容量为16
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //新阈值为 0.75(加载因子) * 16(默认容器容量) = 12
}
if (newThr == 0) { //如果新阈值等于0(构造初始化给定的容器容量和加载因子)
float ft = (float) newCap * loadFactor; //就用新容器容量 * 初始化加载因子
//新容器容量和计算后的容器容量小于最大值,新阈值等于计算后的值,否则等于Integer最大值(20个亿左右)
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
threshold = newThr; //阈值替换为新阈值
@SuppressWarnings({" rawtypes", "unchecked"})
Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap]; //创建一个新容器数组
table = newTab; //table替换为新数组
if (oldTab != null) { //原数组不为空(拆分链表)
for (int j = 0; j < oldCap; ++j) { //通过原数组长度遍历每个节点(保证不会越界)
Node<K, V> e; //定义一个变量记录每次的节点
if ((e = oldTab[j]) != null) { //节点赋值给e节点如果不为null
oldTab[j] = null; //将这个节点改为null
if (e.next == null) //如果e节点的下一个节点等于null(只有一个节点)
newTab[e.hash & (newCap - 1)] = e; //重新计算节点再新数组中的位置
else if (e instanceof TreeNode) //如果节点是树形结构
((TreeNode<K, V>) e).split(this, newTab, j, oldCap); //进行树结构切割
else { //否则e节点的下一个节点不等于null且不是树形结构
Node<K, V> loHead = null, loTail = null; //lo(当前)头、尾节点(保持秩序)
Node<K, V> hiHead = null, hiTail = null; //hi(新)头、尾节点(保持秩序)
Node<K, V> next; //下一个节点
do {
next = e.next; //保存每次遍历的下一个节点
if ((e.hash & oldCap) == 0) { //如果等于0,说明还是再这个数组里面
if (loTail == null) //尾部插入所以判断尾节点等于null
loHead = e; //头节点等于当前遍历的节点
else //尾节点不为null
loTail.next = e; //尾节点的下一个节点等于当前遍历的节点
loTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系
} else { //否则不等于0,说明再新的数组里面
if (hiTail == null) //尾部插入所以判断尾节点等于null
hiHead = e; //头节点等于当前遍历的节点
else //尾节点不为null
hiTail.next = e; //尾节点的下一个节点等于当前遍历的节点
hiTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系
}
} while ((e = next) != null); //下一个节点不为null,继续遍历
if (loTail != null) { //(当前)尾节点不为空
loTail.next = null; //(当前)尾节点设为null
newTab[j] = loHead; //将重新组合号的链表节点放入当前数组下标中
}
if (hiTail != null) { //(新)尾节点不为空
hiTail.next = null; //(新)尾节点设为null
//将重新组合号的链表节点放入新数组下标(当前遍历下标+原数组容器容量,反正不会超过新数组长度(2倍扩容))中
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
treeify()方法源码分析
//形成从该节点链接的节点树。
final void treeify(Node<K, V>[] tab) {
//root=根节点
TreeNode<K, V> root = null;
//从当前节点开始遍历,定义next节点遍历接收链表的下一个节点
for (TreeNode<K, V> x = this, next; x != null; x = next) {
next = (TreeNode<K, V>) x.next; //x节点不等null将x节点的下一个节点赋值给next节点
x.left = x.right = null; //将x节点的左右子节点设为null
if (root == null) { //如果root节点等于null
x.parent = null; //x节点的父节点等于null
x.red = false; //x节点为黑色
root = x; //x节点为root(根)节点
} else {
K k = x.key; //获取x节点的key
int h = x.hash; //获取x节点的value
Class<?> kc = null; //获取key的类似类
for (TreeNode<K, V> p = root; ; ) { //从root节点开始遍历
int dir, ph;
K pk = p.key; //获取root节点的key
if ((ph = p.hash) > h) //如果root节点的hash值大于x节点的hash值
dir = -1; //dir等于-1表示在左边子节点
else if (ph < h) //否则如果小于x节点的hash值
dir = 1; //dir等于1表示在右边子节点
else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) //否则不满足前两种情况,kc类似类等于null(没有类似类)或者类比较等于0(两个类比较相等)(不做讲解,理解即可)
dir = tieBreakOrder(k, pk); //最终还是无法分辨大小,通过系统hashcode比较得到1或-1,达到平衡
TreeNode<K, V> xp = p; //将p节点赋值xp节点
if ((p = (dir <= 0) ? p.left : p.right) == null) { //p节点等于p节点的左或右子节点等于null
x.parent = xp; //x节点的父节点等于xp节点
if (dir <= 0) //dir小于等于0
xp.left = x; //xp节点的左子节点等于x节点
else //否则大于0
xp.right = x; //xp节点的右子节点等于x节点
root = balanceInsertion(root, x); //平衡操作
break;
}
}
}
}
moveRootToFront(tab, root); //移动root节点到前面
}
moveRootToFront()方法源码分析
/**
* 确保给定的根是其bin的第一个节点。 (移动root节点到前面)
*/
static <K, V> void moveRootToFront(Node<K, V>[] tab, TreeNode<K, V> root) {
int n; //数组长度
if (root != null && tab != null && (n = tab.length) > 0) { //判断root节点不等于null且数组不等于null数组长度大于0
int index = (n - 1) & root.hash; //通过root的hash值计算下标
TreeNode<K, V> first = (TreeNode<K, V>) tab[index]; //获取下标第一个节点
if (root != first) { //判断新的root节点不等于第一个节点
Node<K, V> rn; //root节点的下一个节点
tab[index] = root; //数组下标替换为root节点
TreeNode<K, V> rp = root.prev; //root节点的上一个节点
if ((rn = root.next) != null) //判断root节点的下一个节点不等于null
((TreeNode<K, V>) rn).prev = rp; //rn节点的上一个节点等于rp节点
if (rp != null) //判断rp节点不等于null
rp.next = rn; //rp节点的下一个节点等于rn节点(建立上下关系)
if (first != null) //判断first节点不等于null
first.prev = root; //first节点的上一个节点等于root
root.next = first; //root节点的下一个节点等于first
root.prev = null; //root节点的上一个节点等于null
}
assert checkInvariants(root); //检查根节点是否满足红黑树节点规则
}
}
checkInvariants()方法源码分析
主要进行红黑树结构的校验,从root节点开始,到每一个节点都要进行校验,直至为null返回true,结束校验。
/**
* 检查红黑树准确性
*/
static <K, V> boolean checkInvariants(TreeNode<K, V> t) {
//tp=t节点的父节点、tl=t节点的左子节点、tr=t节点的右子节点、tb=t节点的上一个节点、tn=t节点的下一个节点
TreeNode<K, V> tp = t.parent, tl = t.left, tr = t.right,
tb = t.prev, tn = (TreeNode<K, V>) t.next;
//判断tb节点不等于null且下一个节点不等于t节点
if (tb != null && tb.next != t)
return false; //返回false
//判断tn节点不等于null且tn的上一个节点不等于t节点
if (tn != null && tn.prev != t)
return false; //返回false
//判断tp节点不等于null且t节点不等于tp的左右节点
if (tp != null && t != tp.left && t != tp.right)
return false; //返回false
判断tl节点不等于null且tl节点的父节点不等于t节点或者tl节点的hash值大于t节点的hash值
if (tl != null && (tl.parent != t || tl.hash > t.hash))
return false; //返回false
//判断tr节点不等于null且tr节点的父节点不等于t节点或者tr节点的hash值小于t节点的hash值
if (tr != null && (tr.parent != t || tr.hash < t.hash))
return false; //返回false
//判断t节点、tl节点、tr节点不等于null且都是红色
if (t.red && tl != null && tl.red && tr != null && tr.red)
return false; //返回false
//tl节点不等于null且从tl节点递归检测为false
if (tl != null && !checkInvariants(tl))
return false; //返回false
//tr节点不等于null且从tr节点递归检测为false
if (tr != null && !checkInvariants(tr))
return false; //返回false
return true; //返回true
}
root()方法源码分析
//返回节点的根节点
final TreeNode<K,V> root() {
//从当前节点开始遍历
for (TreeNode<K,V> r = this, p; ; ) {
if ((p = r.parent) == null) //p节点等于r节点的上一个节点等于null
return r; //返回r节点
r = p; //不等于null,r节点等于p节点往上寻找
}
}
find()方法源码分析
//寻找左右子节点是否有相同节点
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this; //p=当前节点
do {
int ph, dir;
K pk; //ph=p节点的hash值、dir=存放位置遍历、p=p节点key值
TreeNode<K,V> pl = p.left, pr = p.right, q; //pl=p节点的左子节点、 pr=p节点的右子节点、q=相同节点
if ((ph = p.hash) > h) //p节点hash值大于h
p = pl; //p节点等于pl节点
else if (ph < h) // p节点hash值小于h
p = pr; // p节点等于pr节点
else if ((pk = p.key) == k || (k != null && k.equals(pk))) //key值相同
return p; //返回p节点
else if (pl == null) //pl节点等于null
p = pr; //p节点等于pr节点
else if (pr == null) //pr节点等于null
p = pl; //p节点等于pl节点
//否则不满足前几种情况,kc类似类不等于null(有类似类)或者类比较不等于0(两个类比较不相等)(不做讲解,理解即可)
else if ((kc != null || (kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr; //dir小于0从p节点等于pl节点否则等于pr节点
else if ((q = pr.find(h, k, kc)) != null) //q节点等于pr节点开始匹配且不等于null
return q; //返回q节点
else //否则
p = pl; //p节点等于pl节点
} while (p != null); //p节点不等于null,进入循环结构
return null; // p节点等于null,节点循环,返回null
}
put()方法源码分析
/**
* 将指定的值与指定的键关联,如果键已存在,则替换原来的值
*/
public V put(K key, V value) {
//调用putVal()
return putVal(hash(key), key, value, false, true);
}
//通过key计算hash值、传入key,value、 onlyIfAbsent等于false替换重复value、 evict表示创建模式,用于平衡操作
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
//tab=数组容器,p=数组的某下标头节点,n=数组长度,i=数组某下标位
Node<K, V>[] tab; Node<K, V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) //table等于null或者长度为0
n = (tab = resize()).length; //初始化数组容器容量(无参构造,第一次put()时)
if ((p = tab[i = (n - 1) & hash]) == null) //如果数组计算后的下标等于null
tab[i] = newNode(hash, key, value, null); //在此下标的位置上创建第一个节点
else { //else说明此下标已存在节点元素
Node<K, V> e;K k; //e = 节点元素,k = 键
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //如果hash值与key相同
e = p; //e节点等于下标p节点
else if (p instanceof TreeNode) //如果p节点是树形节点
//调用树形putTreeVal()方法
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
else { //key不相同且非树形结构
for (int binCount = 0; ; ++binCount) { //遍历每个节点
if ((e = p.next) == null) { //p节点的下一个节点等于null
p.next = newNode(hash, key, value, null); //p节点的下一个节点等于创建的新节点
if (binCount >= TREEIFY_THRESHOLD - 1) //遍历次数大于树形阈值7
treeifyBin(tab, hash); //进行树形化
break; //结束循环
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; //遍历过程中hash值与key值相同,结束循环
p = e; //p替换为下一个节点,直到节点为null
}
}
if (e != null) { //e节点不为null,说明有相同节点
V oldValue = e.value; //获取e节点的value值
if (!onlyIfAbsent || oldValue == null) // onlyIfAbsent等于false或者原value为null
e.value = value; //将节点的value值替换为传入的value值
afterNodeAccess(e); //平衡操作,HashMap未实现代码,LinkedHashMap讲解
return oldValue; //返回原value值
}
}
++modCount; //操作次数加1
if (++size > threshold) //如果大小加1后大于阈值
resize(); //重置大小
afterNodeInsertion(evict); //平衡操作,HashMap未实现代码,LinkedHashMap讲解
return null; //返回null
}
put()方法案例讲解
//通过key计算hash值、传入key,value、 onlyIfAbsent等于false替换重复value、 evict表示创建模式,用于平衡操作
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
//tab=数组容器,p=数组的某下标头节点,n=数组长度,i=数组某下标位
Node<K, V>[] tab; Node<K, V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) //table等于null或者长度为0
n = (tab = resize()).length; //初始化数组容器容量(无参构造,第一次put()时)
if ((p = tab[i = (n - 1) & hash]) == null) //如果数组计算后的下标等于null
tab[i] = newNode(hash, key, value, null); //在此下标的位置上创建第一个节点
//省略部分代码...
++modCount; //操作次数加1
if (++size > threshold) //如果大小加1后大于阈值
resize(); //重置大小
afterNodeInsertion(evict); //平衡操作,HashMap未实现代码,LinkedHashMap讲解
return null; //返回null
}
/**
* 初始化大小,如果为空分配容器大小及阈值,否则以2的幂进行扩容
*/
final Node<K, V>[] resize() {
Node<K, V>[] oldTab = table; //原节点数组,第一次为null
int oldCap = (oldTab == null) ? 0 : oldTab.length; //原数组容器容量默认为0
int oldThr = threshold; //原阈值,默认0
int newCap, newThr = 0; //新容器、阈值为0
//省略部分代码
else { //原容器容量和原阈值都为0(无参构造,第一次进来分配默认大小)
newCap = DEFAULT_INITIAL_CAPACITY; //新容器容量为16
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //新阈值为 0.75(加载因子) * 16(默认容器容量) = 12
}
if (newThr == 0) { //如果新阈值等于0(构造初始化给定的容器容量和加载因子)
float ft = (float) newCap * loadFactor; //就用新容器容量 * 初始化加载因子
//新容器容量和计算后的容器容量小于最大值,新阈值等于计算后的值,否则等于Integer最大值(20个亿左右)
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
threshold = newThr; //阈值替换为新阈值
@SuppressWarnings({" rawtypes", "unchecked"})
Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap]; //创建一个新容器数组
table = newTab; //table替换为新数组
if (oldTab != null) { //原数组不为空(拆分链表)
//省略部分代码...
}
return newTab;
}
假设:创建了new HashMap()的无参构造函数
//通过key计算hash值、传入key,value、 onlyIfAbsent等于false替换重复value、 evict表示创建模式,用于平衡操作
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
//tab=数组容器,p=数组的某下标头节点,n=数组长度,i=数组某下标位
Node<K, V>[] tab; Node<K, V> p; int n, i;
//省略部分代码...
else { //else说明此下标已存在节点元素
Node<K, V> e;K k; //e = 节点元素,k = 键
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //如果hash值与key相同
e = p; //e节点等于下标p节点
else if (p instanceof TreeNode) //如果p节点是树形节点
//调用树形putTreeVal()方法
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
else { //key不相同且非树形结构
for (int binCount = 0; ; ++binCount) { //遍历每个节点
if ((e = p.next) == null) { //p节点的下一个节点等于null
p.next = newNode(hash, key, value, null); //p节点的下一个节点等于创建的新节点
if (binCount >= TREEIFY_THRESHOLD - 1) //遍历次数大于树形阈值7
treeifyBin(tab, hash); //进行树形化
break; //结束循环
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; //遍历过程中hash值与key值相同,结束循环
p = e; //p替换为下一个节点,直到节点为null
}
}
if (e != null) { //e节点不为null,说明有相同节点
V oldValue = e.value; //获取e节点的value值
if (!onlyIfAbsent || oldValue == null) // onlyIfAbsent等于false或者原value为null
e.value = value; //将节点的value值替换为传入的value值
afterNodeAccess(e); //平衡操作,HashMap未实现代码,LinkedHashMap讲解
return oldValue; //返回原value值
}
}
++modCount; //操作次数加1
if (++size > threshold) //如果大小加1后大于阈值
resize(); //重置大小
afterNodeInsertion(evict); //平衡操作,HashMap未实现代码,LinkedHashMap讲解
return null; //返回null
}
第二次put
第三次put
第四次put
putVal()方法:小结
- 前面介绍了以Map的无参构造进行的数组为空扩容、数组下标值替换、链表节点元素追加和链表值替换。
- 数组为空。先进行第一次扩容,扩容完毕,再计算节点所在数组的位置,当计算后数组位置没有数据时,就会在这个数组的位置上创建第一个节点。然后累计操作和元素数量累加,完成第一次put。
- 数组下标替换。第一次扩容后不会在进行扩容且当前数组下标元素也不为空,不会再相同位置创建节点元素,然后判断数组下标第一个节点key值和当前传入的key值相同,e节点不为空,然后替换为传入的value值,返回原value值。完成数组下标替换。
- 链表节点元素追加。从第一个节点开始遍历,判断遍历节点的下一个节点为空,然后再创建一个新节点成为遍历节点的下一个节点,再判断是否需要树形化(树形化的条件要满足链表长度为8也就是说链表长度最多不超过8且数组容器值满足最小树形化大小64才可以进行树形化,后序会讲解),结束循环,e节点为空,不进行元素替换,累计操作次数和元素数量累加,完成链表节点追加。
- 链表值替换。从第一个节点开始遍历,判断遍历节点的下一个节点不为空,再判断当前遍历节点的下一个节点key值和传入的key值相同,结束循环。e节点不为空,然后替换为传入的value值,返回原value值。完成链表值替换。
resize()方法案例讲解
再来讲解下指定容器大小的put()方法,顺便讲解扩容操作。
//有参构造,传入指定容器容量及加载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: "+initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: "
+loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
//有参构造,传入指定容器容量,但使用默认加载因子
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//返回给定目标容量大小的2次方(通过位移和或运算计算出阈值,基本位移2位或4位时基本已经确定大小了)。
static final int tableSizeFor(int cap) {
//n = 2-1 = 1 = 0001
int n = cap - 1;
//0000 | 0001 = 0001 =1
n |= n >>> 1;
//0000 | 0001 = 0001 =1
n |= n >>> 2;
//0000 0000 | 0000 0001 = 0001 =1
n |= n >>> 4;
//0000 0000 0000 | 0000 0000 0001 = 0001 =1
n |= n >>> 8;
//0000 0000 0000 0000 | 0000 0000 0000 0001 = 0001 =1
n |= n >>> 16;
//如果n小于0返回1,否则大于最大容器容量返回最大容器容量,否则返回n+1=2
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
阈值计算返回容器最接近的2次幂值,这样做都是为了扩容。先将容器值减一(为了防止本身传参就是2的幂,如果本身就是2的幂,那么计算完还是本身值)。注意此方法是计算最接近2次幂的值,下面用2,3,4,5分别讲解,强化理解。注意:|表示或位运算符,二进制的两个数之间有一个为1则为1否则为0,>>>表示无符号右移,左边最高位之间补0。
/**
* 初始化大小,如果为空分配容器大小及阈值,否则以2的幂进行扩容
*/
final Node<K, V>[] resize() {
Node<K, V>[] oldTab = table; //原节点数组,第一次为null
int oldCap = (oldTab == null) ? 0 : oldTab.length; //原数组容器容量默认为0
int oldThr = threshold; //原阈值,默认0
int newCap, newThr = 0; //新容器、阈值为0
//省略部分代码
else if (oldThr > 0) //原阈值大于0(初始化构造给定的阈值)
newCap = oldThr; //新容器容量等于传入的阈值
//省略部分代码
if (newThr == 0) { //如果新阈值等于0(构造初始化给定的容器容量和加载因子)
float ft = (float) newCap * loadFactor; //就用新容器容量 * 初始化加载因子
//新容器容量和计算后的容器容量小于最大值,新阈值等于计算后的值,否则等于Integer最大值(20个亿左右)
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
threshold = newThr; //阈值替换为新阈值
@SuppressWarnings({" rawtypes", "unchecked"})
Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap]; //创建一个新容器数组
table = newTab; //table替换为新数组
//省略部分代码...
return newTab;
}
稍微介绍有参构造函数的put操作,主要讲解扩容
/**
* 初始化大小,如果为空分配容器大小及阈值,否则以2的幂进行扩容
*/
final Node<K, V>[] resize() {
Node<K, V>[] oldTab = table; //原节点数组,第一次为null
int oldCap = (oldTab == null) ? 0 : oldTab.length; //原数组容器容量默认为0
int oldThr = threshold; //原阈值,默认0
int newCap, newThr = 0; //新容器、阈值为0
if (oldCap > 0) { //如果原容器容量大于0 (非第一次扩容)
if (oldCap >= MAXIMUM_CAPACITY) { //如果原容器容量大于最大值1的30次幂(10个亿左右)
threshold = Integer.MAX_VALUE; //阈值等于Integer最大值(20个亿左右)
return oldTab; //返回原数组
}
//新容器容量等于2倍原容器容量且小于最大容器容量且原容器容量大于默认容器值(16)
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; //新阈值等于原阈值的2倍
}//省略部分代码
if (newThr == 0) { //如果新阈值等于0(构造初始化给定的容器容量和加载因子)
float ft = (float) newCap * loadFactor; //就用新容器容量 * 初始化加载因子
//新容器容量和计算后的容器容量小于最大值,新阈值等于计算后的值,否则等于Integer最大值(20个亿左右)
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
threshold = newThr; //阈值替换为新阈值
@SuppressWarnings({" rawtypes", "unchecked"})
Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap]; //创建一个新容器数组
table = newTab; //table替换为新数组
if (oldTab != null) { //原数组不为空(拆分链表)
for (int j = 0; j < oldCap; ++j) { //通过原数组长度遍历每个节点(保证不会越界)
Node<K, V> e; //定义一个变量记录每次的节点
if ((e = oldTab[j]) != null) { //节点赋值给e节点如果不为null
oldTab[j] = null; //将这个节点改为null
if (e.next == null) //如果e节点的下一个节点等于null(只有一个节点)
newTab[e.hash & (newCap - 1)] = e; //重新计算节点再新数组中的位置
else if (e instanceof TreeNode) //如果节点是树形结构
((TreeNode<K, V>) e).split(this, newTab, j, oldCap); //进行树结构切割
else { //否则e节点的下一个节点不等于null且不是树形结构
Node<K, V> loHead = null, loTail = null; //lo(当前)头、尾节点(保持秩序)
Node<K, V> hiHead = null, hiTail = null; //hi(新)头、尾节点(保持秩序)
Node<K, V> next; //下一个节点
do {
next = e.next; //保存每次遍历的下一个节点
if ((e.hash & oldCap) == 0) { //如果等于0,说明还是再这个数组里面
if (loTail == null) //尾部插入所以判断尾节点等于null
loHead = e; //头节点等于当前遍历的节点
else //尾节点不为null
loTail.next = e; //尾节点的下一个节点等于当前遍历的节点
loTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系
} else { //否则不等于0,说明再新的数组里面
if (hiTail == null) //尾部插入所以判断尾节点等于null
hiHead = e; //头节点等于当前遍历的节点
else //尾节点不为null
hiTail.next = e; //尾节点的下一个节点等于当前遍历的节点
hiTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系
}
} while ((e = next) != null); //下一个节点不为null,继续遍历
if (loTail != null) { //(当前)尾节点不为空
loTail.next = null; //(当前)尾节点设为null
newTab[j] = loHead; //将重新组合号的链表节点放入当前数组下标中
}
if (hiTail != null) { //(新)尾节点不为空
hiTail.next = null; //(新)尾节点设为null
newTab[j + oldCap] = hiHead; //将重新组合号的链表节点放入新数组下标(当前遍历下标+原数组容器容量,反正不会超过新数组长度(2倍扩容))中
}
}
}
}
}
return newTab;
}
//对于给定的哈希,替换bin中的所有链接节点,除非表太小,在这种情况下,调整大小。
final void treeifyBin(Node<K,V>[] tab, int hash) {
//n=数组长度,index=计算新元素的数组位置,e=数组位置节点
int n, index;Node<K,V> e;
//如果数组为null或者数组长度小于最小树形化容器64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); //调整大小
//省略部分代码
}
resize()方法源码:小结
- Map的扩容一共分为三种情况:原容器值大于0、原阈值大于0、原容器值和阈值等于0。
- 原容器值大于0。说明数组不为null且已分配容器大小、有元素的时候;如果原容器值大于最大容器值,阈值直接为Integer的最大值。返回原数组,此时不可能再扩容;否则新容器值等于原数组容器扩容两倍后小于最大容器值且原数组容器大于等于默认容器大小,那么新阈值为原阈值的两倍,如果不满足默认初始化容器大小就会进入另外一种情况用新容器值和加载因子重新计算新阈值。
- 原阈值大于0。说明已分配数组的阈值但未分配容器大小,也就是初始化时定义好的初始容器值的情况,这种情况新容器大小等于阈值。
- 原容器值和阈值等于0。说明不满足前面两种条件,无参构造,第一次put,未分配初始化阈值,使用默认的参数定义和计算新容器值和阈值。
- 阈值是用来做数组扩容的条件,数组容器是存放元素的空间,阈值比数组容器小(如果加载因子为0.75,也就是数组长度的3/4)。
- 如果原数组不为空,遍历原数组将每个下标的元素拆分重组后存放新数组中,也有三种情况:下标元素没有下一个节点、此下标节点为树形化节点、下标元素的下一个节点不为null且非树形化节点。
- 下标元素没有下一个节点。如果下标元素的下一个节点等于null,重新计算元素存放再新数组的位置。
- 此下标节点为树形化节点。如果是树形化节点就会进行树形化切割。
- 下标元素的下一个节点不为null且非树形化节点。遍历下标的每一个节点,并且通过计算将节点位置分为原位置和新位置,等于0的就会连接成一串新链表存放新数组的(当前遍历位置)原下标位置,不等于0的也会连接成一串新链表存放再新数组的当前遍历位置+原容器值的位置,这样做不会超过新数组容器值;
resize()方法案例讲解
再来完整的讲解下树形化链表的方法
//对于给定的哈希,替换bin中的所有链接节点,除非表太小,在这种情况下,调整大小。
final void treeifyBin(Node<K,V>[] tab, int hash) {
//n=数组长度,index=计算新元素的数组位置,e=数组位置节点
int n, index;Node<K,V> e;
//如果数组为null或者数组长度小于最小树形化容器64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); //调整大小
//否则通过hash值计算后下标数组不等于null(当前存放下标位置),重新建立连接
else if ((e = tab[index = (n - 1) & hash]) != null) {
//hd=头节点,t1=存放p节点,建立上下关系
TreeNode<K,V> hd = null, tl = null;
do {
//将节点变成树节点
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null) //如果t1等于null
hd = p; //头节点为数组的第一个节点
else { //否则
p.prev = tl; //p节点的上一个节点赋值为t1节点
tl.next = p; //t1的下一个节点赋值为p节点,建立上下关系
}
tl = p; //p节点赋值给t1节点
} while ((e = e.next) != null); //每次遍历e节点替换为下一个节点,直到等于null结束循环
if ((tab[index] = hd) != null) //将建立好关系的hd节点重新赋值给数组位置
hd.treeify(tab); //将数组进行树形化
}
}
treeifyBin()方法:小结
- treeifyBin()方法主要是用来将节点转换为树形节点,进行树形化必须得达到链表长度大于等于8且数组长度超过最小树形化容器64,才能转换为树结构。
- 数组长度未达到要求时,重新进行扩容操作。
- 数组长度达到要求时,通过当前传入的hash值获取数组下标元素不等于null,将节点转换为树形化节点,然后使得上一个节点和下一个节点建立双向关系。
treeify()方法案例讲解
//形成从该节点链接的节点树。
final void treeify(Node<K, V>[] tab) {
//root=根节点
TreeNode<K, V> root = null;
//从当前节点开始遍历,定义next节点遍历接收链表的下一个节点
for (TreeNode<K, V> x = this, next; x != null; x = next) {
next = (TreeNode<K, V>) x.next; //x节点不等null将x节点的下一个节点赋值给next节点
x.left = x.right = null; //将x节点的左右子节点设为null
if (root == null) { //如果root节点等于null
x.parent = null; //x节点的父节点等于null
x.red = false; //x节点为黑色
root = x; //x节点为root(根)节点
} else {
K k = x.key; //获取x节点的key
int h = x.hash; //获取x节点的value
Class<?> kc = null; //获取key的类似类
for (TreeNode<K, V> p = root; ; ) { //从root节点开始遍历
int dir, ph;
K pk = p.key; //获取root节点的key
if ((ph = p.hash) > h) //如果root节点的hash值大于x节点的hash值
dir = -1; //dir等于-1表示在左边子节点
else if (ph < h) //否则如果小于x节点的hash值
dir = 1; //dir等于1表示在右边子节点
else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) //否则不满足前两种情况,kc类似类等于null(没有类似类)或者类比较等于0(两个类比较相等)(不做讲解,理解即可)
dir = tieBreakOrder(k, pk); //最终还是无法分辨大小,通过系统hashcode比较得到1或-1,达到平衡
TreeNode<K, V> xp = p; //将p节点赋值xp节点
if ((p = (dir <= 0) ? p.left : p.right) == null) { //p节点等于p节点的左或右子节点等于null
x.parent = xp; //x节点的父节点等于xp节点
if (dir <= 0) //dir小于等于0
xp.left = x; //xp节点的左子节点等于x节点
else //否则大于0
xp.right = x; //xp节点的右子节点等于x节点
root = balanceInsertion(root, x); //平衡操作
break;
}
}
}
}
moveRootToFront(tab, root); //移动root节点到前面
}
treeify()方法源码:小结
- treeify()方法主要是用来将节点顺序转换为树形节点顺序,从当前节点开始往下遍历每一个节点。主要两种情况:根节点等于null的情况、根节点不等于null的情况。
- 当根根等于null。当前遍历节点的父节点设为null,再将颜色设为黑色,成为根节点。
- 根节点不等于null。从根节点开始遍历,通过当前遍历节点hash值和root节点往下的每个节点的hash值进行判断,如果当前遍历节点大于根节点往下遍历的节点,就成为当前根节点往下遍历节点的右子节点,否则就是左子节点,如果左或右子节点存在节点,那么下次遍历就会从左或右子节点开始,重新进行判断直至左或右子节点等于null时,建立关系。
balanceInsertion()方法源码分析
//平衡插入,和TreeMap的fixAfterInsertion()方法类似
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) {
x.red = true; //进来的x节点为红色
//循环定义xp=x节点的父节点,xpp=x节点的父父(爷)节点,xppl=父父(爷)节点的左子节点, xppr=父父(爷)节点的右子节点
for (TreeNode<K,V> xp, xpp, xppl, xppr; ; ) {
if ((xp = x.parent) == null) { //x节点的父节点赋值给xp节点判断等于null
x.red = false; //将x节点设为黑色
return x; //返回x节点
//否则xp节点为黑色或者xp节点的父节点赋值给xpp节点等于null
} else if (!xp.red || (xpp = xp.parent) == null)
return root; //返回root节点
if (xp == (xppl = xpp.left)) { //如果xp节点等于xpp节点的左子节点赋值给xppl节点
//判断xpp节点的右子节点赋值给xppr节点不等于null且xppr节点为红色
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false; //xppr节点设为黑色
xp.red = false; //xp节点设为黑色
xpp.red = true; //xpp节点设为红色
x = xpp; //x节点等于xpp节点
} else { //否则xppr等于null或者为黑色
if (x == xp.right) { //x节点等于xp节点的右子节点
root = rotateLeft(root, x = xp); //从xp节点开始左旋
//xp等于x节点的父节点等于null,xpp为null否则为xp的父节点
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) { //判断xp节点不等于null
xp.red = false; //xp节点设为黑色
if (xpp != null) { //判断xpp节点不等于null
xpp.red = true; //xpp节点设为红色
root = rotateRight(root, xpp); //从xpp节点开始右旋
}
}
}
} else { //否则xp节点不等于xpp节点的左子节点
//判断xpp节点的左子节点赋值给xppl节点不等于null且xppl节点为红色
if (xppl != null && xppl.red) {
xppl.red = false; //xppl节点设为黑色
xp.red = false; //xp节点设为黑色
xpp.red = true; //xpp节点设为红色
x = xpp; //x节点等于xpp节点
} else { //否则xppl等于null或者为黑色
if (x == xp.left) { //x节点等于xp节点的左子节点
root = rotateRight(root, x = xp); //从xp节点开始左旋
//xp等于x节点的父节点等于null,xpp为null否则为xp的父节点
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) { //判断xp节点不等于null
xp.red = false; //xp节点设为黑色
if (xpp != null) { //判断xpp节点不等于null
xpp.red = true; //xpp节点设为红色
root = rotateLeft(root, xpp); //从xpp节点开始右旋
}
}
}
}
}
}
案例讲解
//右旋
static <K, V> TreeNode<K, V> rotateRight(TreeNode<K, V> root, TreeNode<K, V> p) {
//l=p节点的左子节点,pp=p节点的父节点,lr=l节点的右子节点
TreeNode<K, V> l, pp, lr;
//判断p节点不等于null且l节点等于p节点的左子节点不等于null
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null) //判断l节点的右子节点等于p节点的左子节点赋值给lr节点不等null
lr.parent = p; //lr节点的父节点等于p节点
if ((pp = l.parent = p.parent) == null) //p节点的父节点等于l节点的父节点等于pp节点
(root = l).red = false; //root节点等于l节点,设为黑色
else if (pp.right == p) //pp节点的右子节点等于p节点
pp.right = l; //pp节点的右子节点等于l节点
else //否则pp节点的右子节点不是p节点
pp.left = l; //pp节点的左子节点等于l节点
l.right = p; //l节点的右子节点等于p节点
p.parent = l; //p节点的父节点等于l节点
}
return root; //返回root节点
}
//左旋
static <K,V> TreeNode <K,V> rotateLeft(TreeNode <K,V> root, TreeNode <K,V> p)
{
//r=p节点的右子节点,pp=p节点的父节点,rl=r节点的左子节点
TreeNode <K,V> r, pp, rl;
//判断p节点不等于null且r节点等于p节点的右子节点不等于null
if(p != null && (r = p.right) != null)
{
if((rl = p.right = r.left) != null) //判断r节点的左子节点等于p节点的右子节点赋值给rl节点不等null
rl.parent = p; //rl节点的父节点等于p节点
if((pp = r.parent = p.parent) == null) //p节点的父节点等于r节点的父节点等于pp节点
(root = r).red = false; //root节点等于r节点,设为黑色
else if(pp.left == p) //pp节点的左子节点等于p节点
pp.left = r; //pp节点的左子节点等于r节点
else //否则pp节点的左子节点不是p节点
pp.right = r; //pp节点的右子节点等于r节点
r.left = p; //r节点的左子节点等于p节点
p.parent = r; //p节点的父节点等于r节点
}
return root; //返回root节点
}
/**
* 确保给定的根是其bin的第一个节点。 (移动root节点到前面)
*/
static <K, V> void moveRootToFront(Node<K, V>[] tab, TreeNode<K, V> root) {
int n; //数组长度
if (root != null && tab != null && (n = tab.length) > 0) { //判断root节点不等于null且数组不等于null数组长度大于0
int index = (n - 1) & root.hash; //通过root的hash值计算下标
TreeNode<K, V> first = (TreeNode<K, V>) tab[index]; //获取下标第一个节点
if (root != first) { //判断新的root节点不等于第一个节点
Node<K, V> rn; //root节点的下一个节点
tab[index] = root; //数组下标替换为root节点
TreeNode<K, V> rp = root.prev; //root节点的上一个节点
if ((rn = root.next) != null) //判断root节点的下一个节点不等于null
((TreeNode<K, V>) rn).prev = rp; //rn节点的上一个节点等于rp节点
if (rp != null) //判断rp节点不等于null
rp.next = rn; //rp节点的下一个节点等于rn节点(建立上下关系)
if (first != null) //判断first节点不等于null
first.prev = root; //first节点的上一个节点等于root
root.next = first; //root节点的下一个节点等于first
root.prev = null; //root节点的上一个节点等于null
}
assert checkInvariants(root); //检查根节点是否满足红黑树节点规则
}
}
/**
* 检查红黑树准确性
*/
static <K, V> boolean checkInvariants(TreeNode<K, V> t) {
//tp=t节点的父节点、tl=t节点的左子节点、tr=t节点的右子节点、tb=t节点的上一个节点、tn=t节点的下一个节点
TreeNode<K, V> tp = t.parent, tl = t.left, tr = t.right,
tb = t.prev, tn = (TreeNode<K, V>) t.next;
//判断tb节点不等于null且下一个节点不等于t节点
if (tb != null && tb.next != t)
return false; //返回false
//判断tn节点不等于null且tn的上一个节点不等于t节点
if (tn != null && tn.prev != t)
return false; //返回false
//判断tp节点不等于null且t节点不等于tp的左右节点
if (tp != null && t != tp.left && t != tp.right)
return false; //返回false
判断tl节点不等于null且tl节点的父节点不等于t节点或者tl节点的hash值大于t节点的hash值
if (tl != null && (tl.parent != t || tl.hash > t.hash))
return false; //返回false
//判断tr节点不等于null且tr节点的父节点不等于t节点或者tr节点的hash值小于t节点的hash值
if (tr != null && (tr.parent != t || tr.hash < t.hash))
return false; //返回false
//判断t节点、tl节点、tr节点不等于null且都是红色
if (t.red && tl != null && tl.red && tr != null && tr.red)
return false; //返回false
//tl节点不等于null且从tl节点递归检测为false
if (tl != null && !checkInvariants(tl))
return false; //返回false
//tr节点不等于null且从tr节点递归检测为false
if (tr != null && !checkInvariants(tr))
return false; //返回false
return true; //返回true
}
再来讲解左节点的平衡方式
//平衡插入,和TreeMap的fixAfterInsertion()方法类似
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) {
x.red = true; //进来的x节点为红色
//循环定义xp=x节点的父节点,xpp=x节点的父父(爷)节点,xppl=父父(爷)节点的左子节点, xppr=父父(爷)节点的右子节点
for (TreeNode<K,V> xp, xpp, xppl, xppr; ; ) {
if ((xp = x.parent) == null) { //x节点的父节点赋值给xp节点判断等于null
x.red = false; //将x节点设为黑色
return x; //返回x节点
//否则xp节点为黑色或者xp节点的父节点赋值给xpp节点等于null
} else if (!xp.red || (xpp = xp.parent) == null)
return root; //返回root节点
if (xp == (xppl = xpp.left)) { //如果xp节点等于xpp节点的左子节点赋值给xppl节点
//判断xpp节点的右子节点赋值给xppr节点不等于null且xppr节点为红色
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false; //xppr节点设为黑色
xp.red = false; //xp节点设为黑色
xpp.red = true; //xpp节点设为红色
x = xpp; //x节点等于xpp节点
} else { //否则xppr等于null或者为黑色
if (x == xp.right) { //x节点等于xp节点的右子节点
root = rotateLeft(root, x = xp); //从xp节点开始左旋
//xp等于x节点的父节点等于null,xpp为null否则为xp的父节点
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) { //判断xp节点不等于null
xp.red = false; //xp节点设为黑色
if (xpp != null) { //判断xpp节点不等于null
xpp.red = true; //xpp节点设为红色
root = rotateRight(root, xpp); //从xpp节点开始右旋
}
}
}
}
//省略部分代码...
}
}
}
balanceInsertion()方法源码:小结
- 插入平衡操作主要为了保持插入的每个节点,都能满足红黑树的特性,保持树形结构
putTreeVal()方法源码分析
/**
* 树版本的putVal
*/
final TreeNode<K, V> putTreeVal(HashMap<K, V> map, Node<K, V>[] tab, int h, K k, V v) {
Class<?> kc = null; //key类似的类
boolean searched = false; //搜索
TreeNode<K, V> root = (parent != null) ? root() : this; //如果父节点不等于null,调用root()方法获取根节点,否则根节点等于当前节点
for (TreeNode<K, V> p = root; ; ) { //从root节点开始遍历
int dir, ph;
K pk; //dir=存放位置变量,ph=p节点hash值、pk=p节点key值
if ((ph = p.hash) > h) //如果p节点的hash值大于h
dir = -1; //dir等于-1表示左边子节点
else if (ph < h) //如果p节点的hash值小于h
dir = 1; //dir等于1表示在右边子节点
else if ((pk = p.key) == k || (k != null && k.equals(pk))) //如果key值都相等
return p; //返回p节点
//否则不满足前两种情况,kc类似类等于null(没有类似类)或者类比较等于0(两个类比较相等)(不做讲解,理解即可)
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) { //搜索变量为false(不做讲解理解即可)
TreeNode<K, V> q, ch;
searched = true; //将搜索变量改为true
//p节点左子节点不等于null,从左子节点开始往下寻找,p节点的右子节点不等于null,从右子节点开始往下寻找,如果匹配相同节点
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; //返回q节点
}
dir = tieBreakOrder(k, pk); //最终还是无法分辨大小,通过系统hashcode比较得到1或-1,达到平衡
}
TreeNode<K, V> xp = p; //p节点赋值给xp节点
if ((p = (dir <= 0) ? p.left : p.right) == null) { //判断dir小于等于0,p节点等于p的左子节点否则等于p的右子节点等于null
Node<K, V> xpn = xp.next; //xp节点的下一个节点
TreeNode<K, V> x = map.newTreeNode(h, k, v, xpn); //创建一个新的链表节点且建立下一个节点关系
if (dir <= 0) //dir节点小于等于0
xp.left = x; //xp节点的左子节点等于x节点
else //否则dir大于0
xp.right = x; //xp节点的右子节点等于x节点
xp.next = x; //xp节点的下一个节点等于x节点
x.parent = x.prev = xp; //xp节点等于x节点的上一个节点等于x节点的父节点
if (xpn != null) //xpn节点不等于null
((TreeNode<K, V>) xpn).prev = x; //xpn的上一个节点等于x节点
moveRootToFront(tab, balanceInsertion(root, x)); //平衡后移动root节点到前面
return null; //返回null
}
}
}
//返回节点的根节点
final TreeNode<K,V> root() {
//从当前节点开始遍历
for (TreeNode<K,V> r = this, p; ; ) {
if ((p = r.parent) == null) //p节点等于r节点的上一个节点等于null
return r; //返回r节点
r = p; //不等于null,r节点等于p节点往上寻找
}
}
//寻找左右子节点是否有相同节点
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this; //p=当前节点
do {
int ph, dir;
K pk; //ph=p节点的hash值、dir=存放位置遍历、p=p节点key值
TreeNode<K,V> pl = p.left, pr = p.right, q; //pl=p节点的左子节点、 pr=p节点的右子节点、q=相同节点
if ((ph = p.hash) > h) //p节点hash值大于h
p = pl; //p节点等于pl节点
else if (ph < h) // p节点hash值小于h
p = pr; // p节点等于pr节点
else if ((pk = p.key) == k || (k != null && k.equals(pk))) //key值相同
return p; //返回p节点
else if (pl == null) //pl节点等于null
p = pr; //p节点等于pr节点
else if (pr == null) //pr节点等于null
p = pl; //p节点等于pl节点
//否则不满足前几种情况,kc类似类不等于null(有类似类)或者类比较不等于0(两个类比较不相等)(不做讲解,理解即可)
else if ((kc != null || (kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr; //dir小于0从p节点等于pl节点否则等于pr节点
else if ((q = pr.find(h, k, kc)) != null) //q节点等于pr节点开始匹配且不等于null
return q; //返回q节点
else //否则
p = pl; //p节点等于pl节点
} while (p != null); //p节点不等于null,进入循环结构
return null; // p节点等于null,节点循环,返回null
}
案例讲解
putTreeVal()方法源码分析:小结
- 树形化插入和treeify()方法有很多相似之处, treeify()方法将树形化结构定义好之后,只要按照这种顺序去插入即可,这里就不做重复的讲解了。不过每次树形化插入,都需要去维护链表的结构,再去进行树的平衡处理。