/**
* 哈希表综合了数组和链表的特点,数组称之为哈希桶,每个桶里面放的是链表,链表中的每个节点,就是哈希表中的每个元素
*/
// 默认的哈希桶初始化容量,必须是2的幂
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 哈希桶最大数量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认装载因子,默认值为0.75
// 如果实际元素数量所占哈希桶分配容量的75%就要扩容,如果占比很大,说明利用空间很多,但是查找效率很低
// 如果占比太小又会导致空间浪费
// 如果关注内存,装载因子可以稍大,如果关注查找性能,装载因子可以稍小
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 哈希桶的树化阈值
// 当桶中元素个数超过这个值时,需要使用红黑树节点替换链表节点
// 这个值必须为8,要不然频繁转换效率也不高
static final int TREEIFY_THRESHOLD = 8;
// 一个树的链表还原阈值
// 扩容时,当桶中元素个数小于这个值时,就会把属性的桶元素还原(切分)为链表结构
// 这个值应该比上面那个值小,至少为6,避免频繁转换
static final int UNTREEIFY_THRESHOLD = 6;
// 哈希表的最小树形化容量
// 当哈希表中的哈希桶数量大于这个值时,哈希桶中的链表才能进行树形化,否则桶内元素太多时会扩容,而不是树形化
// 为了避免进行扩容,树形化选择的冲突,这个值不能小于4 * TREEIFY_THRESHOLD
static final int MIN_TREEIFY_CAPACITY = 64;
// 添加元素
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
// 如果table未初始化就初始化(默认table.size=16,threshold=12)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
// 如果table目标索引值为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))))
// 数组元素key的hash相同,并且key的内存地址或equals相同 ——> key已经存在
e = p;
else if (p instanceof TreeNode)
// 数组元素类型为TreeNode ——> 调用putTreeVal方法插入元素
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 数组元素是链表 ——> 遍历
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
// 连表遍历完 ——> 添加新元素到链表最后位置
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 如果连表长度大于阈值(默认8),就把当前链表转化为红黑树
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 连表中要插入的元素已存在
break;
p = e;
}
}
if (e != null) { // existing mapping for key
// 如果元素已存在,并且符合覆盖条件 ——> 新值覆盖旧值,返回旧值
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 提供一个模板方法
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
// 如果数组元素数量大于阈值,则就进行扩容
resize();
// 提供一个模板方法
afterNodeInsertion(evict);
return null;
}
// 将桶内所有的链表节点替换成红黑树节点
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 如果当前哈希表为空,或者哈希表中哈希桶的数量小于树形化阈值(默认64),就去新建/扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
// 如果哈希表中的哈希桶数量超过了树形化阈值,进行树形化,e是哈希表中指定哈希桶的链表节点头节点
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
// 根据Node链表创建TreeNode节点
do {
// 根据Node节点创建TreeNode节点
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
// 上面只是将链表转换为双链表,下面让桶的第一个元素指向新建的红黑树头节点,
// 以后这个桶里的元素就是红黑树而不是链表了
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
/**
* 在链表中所有的节点都替换成树形节点后需要让桶的第一个元素指向新建的红黑树头结点,这个桶里的元素就是红黑树而不是链表了,
* 之前的操作得到的只能算是双向链表,在调用树形节点hd.treeify(tab)方法进行塑造红黑树,这是HashMap中个人认为第二个比较难得方法
*
*/
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.red = false;
root = x;
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
// 又一个循环,从根节点开始,遍历所有节点跟当前节点x比较,调整位置,有点像冒泡排序
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h) // 当比较节点的哈希值比x大时,dir为-1
dir = -1;
else if (ph < h) // 反之,dir为1
dir = 1;
// 如果两个哈希值相等,且key所在类都实现了comparable接口,就比较key值
else if ((kc == null &&
// 如果k所在类实现了Comparable,就返回k的Class,反之返回null
(kc = comparableClassFor(k)) == null) ||
// 如果pk!=null且pkc和kc都实现了comparable,则k.compareTo(pk)
(dir = compareComparables(kc, k, pk)) == 0)
// 如果两个key值相同,就再比较classname和hash
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
// 如果p.left == null || p.right == null,说明p是叶子 节点
// 如果x比p小且p.left为空,就把x放在p.left上,反之放在p.right上
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
// 修正红黑树
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
/**
* 上面已经将链表转换成了二叉树,下面要把二叉树转换为红黑树
* 红黑树的基本要求:
* 红黑树是一种近似平衡的二叉查找树,它能够确保任何一个节点的左右子树的高度差不会超过二者中较低那个的一倍。
* 它不是严格控制左,右子树高度或节点数之差小于等于1,但红黑树高度依然是log(n),且最坏情况高度不会超过2log(n)。
* 红黑树是满足如下条件的二叉查找树:
* 1. 每个节点要么是红色,要么是黑色
* 2. 根节点必须是黑色
* 3. 红色节点不能连续(也就是,红色节点的子节点和父节点不能是红色)
* 4. 对于每个节点,从该节点至null(树尾端)的任何路径,都含有相同个数的黑色节点
* 下面的balanceInsertion方法需要对树中节点进行重新染色,这个方法也是红黑树插入数据时需要调用的函数,其中涉及到的左旋和右旋操作,这也是红黑树中两个主要操作
*/
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x) {
// 插入的节点都设置为设置为红色
x.red = true;
// 开始循环,循环没有控制条件,只能从内部跳出
// xp: 当前节点的父节点 xpp: 爷爷节点 xppl: 左叔叔节点 xppr: 右叔叔节点
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
// 如果父节点为空,说明当前节点就是根节点,那么把当前节点标为黑色, 返回当前节点
if ((xp = x.parent) == null) { // L1
x.red = false;
return x;
}
// 父节点为黑色,或者【父节点为红色,且爷爷节点为空 -> 这种情况何时出现?】
else if (!xp.red || (xpp = xp.parent) == null) // L2
return root;
if (xp == (xppl = xpp.left)) { // 如果父节点是爷爷节点的左孩子 L3
if ((xppr = xpp.right) != null && xppr.red) { // 如果右叔叔节点不为空,且为红色 L3_1
xppr.red = false; // 右叔叔节点设置为黑色
xp.red = false; // 父节点(左叔叔)设置为黑色
xpp.red = true; // 爷爷节点设置为红色
x = xpp; // 运行到这里之后,就又会进行下一轮循环了,将爷爷节点当作处理的起始节点
}
else { // 如果右叔叔节点为空,或右叔叔节点为黑色 L3_2
if (x == xp.right) { // 如果当前节点是父节点右子节点 L3_2_1
root = rotateLeft(root, x = xp); // 父节点左旋
xpp = (xp = x.parent) == null ? null : xp.parent; // 获取爷爷节点
}
if (xp != null) { // 如果父节点不为空 L3_2_2
xp.red = false; // 父节点设置为黑色
if (xpp != null) { // 如果爷爷节点部位空
xpp.red = true; // 爷爷节点设置为红色
root = rotateRight(root, xpp); // 爷爷节点右旋
}
}
}
}
else { // 如果父节点是爷爷节点的右孩子 L4
if (xppl != null && xppl.red) { // 如果左叔叔不为空,且是红色的 L4_1
xppl.red = false; // 左叔叔节点设置为黑色
xp.red = false; // 父节点(右叔叔)设置为黑色
xpp.red = true; // 爷爷节点设置为红色
x = xpp; // 运行到这里之后,就又会进行下一轮循环,将爷爷节点当作处理的起使节点
}
else { // 如果左叔叔节点为空或是黑色 L4_2
if (x == xp.left) { // 如果当前节点是父节点的左孩子 L4_2_1
root = rotateRight(root, x = xp); // 针对父节点做右旋
xpp = (xp = x.parent) == null ? null : xp.parent; // 获取爷爷节点
}
if (xp != null) { // 如果父节点不为空 L4_2_2
xp.red = false; // 父节点设置为黑色
if (xpp != null) { // 如果爷爷节点不为空
xpp.red = true; // 爷爷节点设置为红色
root = rotateLeft(root, xpp); // 针对爷爷节点做左旋
}
}
}
}
}
}
// 左旋转
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
/**
* p: 当前节点
* r: p的右子节点
* rl: r的左子节点
* pp: p的父节点
*/
TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) {
if ((rl = p.right = r.left) != null) // 如果r的左子节点不为空,就用rl替换r节点位置
rl.parent = p;
if ((pp = r.parent = p.parent) == null) // 如果p的父节点为空,那么就用r做根节点
(root = r).red = false;
else if (pp.left == p) // 如果p是其父节点的左子节点,那么就用r替换p的位置
pp.left = r;
else // 如果p是其父节点的右子节点,那么就用r替换p的位置
pp.right = r;
r.left = p; // 将p放到r的左子节点上
p.parent = r;
}
return root;
}
// 右旋转
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
/**
* p: 当前节点
* l: p的左子节点
* lr: l的右子节点
* pp: p的父节点
*/
TreeNode<K,V> l, pp, lr;
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null) // 如果l的右子节点不为空,就用lr替换l位置
lr.parent = p;
if ((pp = l.parent = p.parent) == null) // 如果p的父节点为空,就用l作为根节点
(root = l).red = false;
else if (pp.right == p) // 如果p是其父节点的右节点,就用l替换p
pp.right = l;
else
pp.left = l; // 如果p是其父节点的左节点,就用l替换p
l.right = p; // 将p节点放到l的右子节点位置上
p.parent = l;
}
return root;
}
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 && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
参考:
https://www.cnblogs.com/winterfells/p/8876888.html
https://blog.csdn.net/weixin_42340670/article/details/80550932