该博客是根据小码哥编程《恋上数据结构与算法》及查看JDK8的HashMap源码所写,理解不当之处请路过滴大佬们斧正!
通过手写HashMap部分源码实现我们将知道:
- 哈希表原理是什么?哈希冲突如何解决?为了减少哈希冲突,HashMap中的哈希值如何计算?
- HashMap的底层数据结构
- HashMap内部的存取逻辑
- HashMap的扩容机制:装填因子是多少?扩容时是直接将元素复制过去吗?
- 初始化桶数组大小是多少?为什么一定是2的幂次方?
- 为什么要求自定义key对象时最好同时重写equals()与hashCode()方法?
…
哈希表
- 也称散列表,典型的以空间换时间。通过哈希函数计算出key值对应的数组索引值,将value存放在数组指定位置。由于可直接定位到指定元素,时间复杂度为O(1)
- 当不同的key值通过哈希函数计算出的索引值相同时,这时我们就称之为哈希冲突。哈希冲突解决方案很多,在Java8中默认采用单向链表将冲突位置的节点串起来,如果单个链表的节点数大于等于9且总节点数大于等于64时,将该单向链表转为红黑树存储。
- 对于哈希值的计算,不同数据类型有不同的实现,也可以我们自己定义计算规则,但是原则都不变:综合利用key的所有信息尽全力生成唯一的哈希值。在HashMap源码中对于hash值的计算是这样的:
在原hashcode的基础上无符号右移16位后再与原值进行异或运算,目的是充分利用所有信息生成最终的hashcode - HashMap中的哈希函数公式如下:
最终的索引值 = (桶数组长度 - 1 后再与hash值进行按位与运算)
到这里我们就可以引出一个问题了,为什么桶数组的容量要保证是2的幂次方?
- 我的回答是——2的幂次方写成二进制形式是以1起头,后面全跟0的样式,减一后得到的是全为1的二进制表示,这个时候再与hash值进行按位与运算,得到的结果一定是小于桶数组长度的!这样就也实现了和取模一样的效果,但是又因为是位运算性能却得到了提升!同时还有一点,按照这样的位运算得出index完全等于hashcode值的后几位数字,由于hashcode值是分布均匀的,故得出的index值也是分布均匀的。
HashMap中插入node的流程
- 由于JDK8中底层采用数组+链表/红黑树的数据结构,执行put方法时也会考虑到节点类型是属于链表节点还是树节点而走不同的执行逻辑。
- 在我们自定义的HashMap中对原生的HashMap进行了简化,只实现了红黑树节点的put逻辑:添加逻辑(当key对应索引位置不为空时)
- 1、先比较hashcode决定往左往右递归
- 2、如果hashcode相同,再调用equals,equals返回为true则执行覆盖操作直接返回结果
- 3、如果equals返回为false,并且传入的key和当前扫描到的key不为空且类型相同且二者按照自定义的比较逻辑结果不为0,则根据返回的cmp值继续向左向右递归node节点
- 4、如果以上条件均不符合并且扫描标志serached = false,扫描红黑树节点,扫描完成后还是没有找到,则serached置为true,
- 同时该节点可以断定一定是新的待添加节点,比较内存地址大小决定往左往右递归待添加节点位置,
- 5、如果递归过程中发现节点已经扫描过了,则直接比较内存地址大小决定继续往哪一边递归,直到node == null时,退出循环添加新节点
第三步在扫描之前再按照比较逻辑比较一次,避免直接进行下一步的扫描,提高了效率
结合代码一起看可以有更直观的理解:
@Override
public V put(K key, V value) {
resize();
int index = index(key);
// 取出index位置的红黑树根节点
Node<K, V> root = table[index];
if (root == null) {
root = new Node<>(key, value, null);
table[index] = root;
size++;
fixAfterPut(root);
return null;
}
// 添加新的节点到红黑树上面
Node<K, V> parent = root;
Node<K, V> node = root;
int cmp = 0;
K k1 = key;
int h1 = hash(k1);
Node<K, V> result = null;
boolean searched = false; // 是否已经搜索过这个key
do {
parent = node;
K k2 = node.key;
int h2 = node.hash;
if (h1 > h2) {
cmp = 1;
} else if (h1 < h2) {
cmp = -1;
} else if (Objects.equals(k1, k2)) {
cmp = 0;
} else if (k1 != null && k2 != null
&& k1 instanceof Comparable
&& k1.getClass() == k2.getClass()
&& (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
} else if (searched) {
// 已经扫描了
cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
} else {
// searched == false; 还没有扫描,然后再根据内存地址大小决定左右
if ((node.left != null && (result = node(node.left, k1)) != null)
|| (node.right != null && (result = node(node.right, k1)) != null)) {
// 已经存在这个key
node = result;
cmp = 0;
} else {
// 扫描完成后也没有找到,故必然是添加新节点
searched = true;
cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
}
}
/*
* 只要cmp != 0就一直递归下去直到 node == null,然后根据cmp的大小(有内存地址大小决定)决定向左向右添加新节点
*/
if (cmp > 0) {
node = node.right;
} else if (cmp < 0) {
node = node.left;
} else {
// 相等执行覆盖操作
V oldValue = node.value;
node.key = key;
node.value = value;
node.hash = h1;
return oldValue;
}
} while (node != null);
// 循环结束后执行添加操作
Node<K, V> newNode = new Node<>(key, value, parent);
if (cmp > 0) {
parent.right = newNode;
} else {
parent.left = newNode;
}
size++;
// 新添加节点之后的处理(保证红黑树的性质)
fixAfterPut(newNode);
return null;
}
与JDK源码中的putTreeVal也是大同小异:
HashMap中寻找node的流程
- 寻找节点时的逻辑与添加时的逻辑大部分保持一致,所不同的是,添加时加入的比较内存地址大小决定向左向右的逻辑在查找node时不能再用了。因为即便是第二次new一个key值完全相同的对象,内存地址也是不一样的。
private Node<K, V> node(K key) {
//找到key值索引位置的红黑树根节点
Node<K, V> root = table[index(key)];
return root == null ? null : node(root, key);
}
/**
* 找到指定key位置的 node节点
* 寻找节点时的逻辑与添加时的逻辑大部分保持一致,所不同的是,添加时加入的比较内存地址大小决定向左向右的逻辑在查找node时不能再用了
*
* @param k1
* @return
*/
private Node<K, V> node(Node<K, V> node, K k1) {
int h1 = hash(k1);
//存储查找结果
Node<K, V> result = null;
int cmp = 0;
while (node != null) {
int h2 = node.hash;
K k2 = node.key;
/* 1.先通过哈希值决定往左还是往右查找 */
if (h1 > h2) {
node = node.right;
} else if (h1 < h2) {
node = node.left;
} /* 2.哈希值相同比较equals */ else if (Objects.equals(k1, k2)) {
return node;
} /* 3.如果哈希值相同但是不equals,按照自定义比较逻辑进行比较 */ else if (k1 != null && k2 != null
&& k1.getClass() == k2.getClass()
&& k1 instanceof Comparable
&& (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
node = cmp > 0 ? node.right : node.left;
} /* 4.如果哈希值相同equals不相等同时也不存在可比较性或者比较结果为0 扫描左右子树 */ else if (node.right != null && (result = node(node.right, k1)) != null) {
return result;
} else {
node = node.left;
}
}
return null;
}
HashMap的扩容机制
- HashMap在扩容时会新建一个容量为OldCap两倍大小的数组 ,然后再将节点元素按照新计算出来的索引值重新排布。注意 ,新建数组扩容后的大小是原数组容量的两倍,这就存在一个规律:新计算出来的节点索引有两种情况:1.索引不变 2.索引index = index + OldLength
- 利用这个规律,我们就不用在扩容时从头计算哈希值了。在JDK8中确实也是这样子做的,以链表的扩容处理为例:
关于equals与hashCode方法
- 通过手写HashMap的put方法实现我们了解到:在决定往哪个位置添加节点时我们先判断的是哈希值,哈希值不同然后才是去调用equals方法。
- 试想我们如果只重写了equals方法但没有重写hashCode方法,可能就会存在两个equals判断为true但是由于hashcode值不同,大概率计算出的索引位置也不同(注意说的是大概率,不同的hashcode值不意味着索引一定不同),哈希冲突没有发生,根本就不会再去调用equals方法。最终导致相同的元素被添加到了map中,违背了key值的唯一性。
- 那如果我们只重写了hashCode方法没有重写equals方法呢?我的答案是——同样的两个对象由于hashcode值相同,索引位置一定相同,必然发生哈希冲突。接下来调用equals方法返回为false,同样造成两个相同的对象都被添加进了HashMap当中。
MyHashMap-完整代码
package com.firework.map;
import java.util.*;
/**
* @author .idea
* @date 2021/4/5
* 与
*/
public class MyHashMap<K, V> implements Map<K, V> {
private static final boolean RED = false;
private static final boolean BLACK = true;
//数组默认容量设置为16
private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//默认装填因子大小
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private int size;
//定义桶数组table
private Node<K, V>[] table;
public MyHashMap() {
table = new Node[DEFAULT_INITIAL_CAPACITY];
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void clear() {
//效率问题,size本来就为0时不再循环
if (size == 0) {
return;
}
size = 0;
Arrays.fill(table, null);
}
/**
* 插入节点,返回oldValue
* 添加逻辑(当key对应索引位置不为空时)
* 1、先比较hashcode决定往左往右递归
* 2、如果hashcode相同,再调用equals,equals返回为true则执行覆盖操作直接返回结果
* 3、如果equals返回为false,并且传入的key和当前扫描到的key不为空且类型相同且二者按照自定义的比较逻辑结果不为0,则根据返回的cmp值继续向左向右递归node节点
* 4、如果以上条件均不符合并且扫描标志serached = false,扫描红黑树节点,扫描完成后还是没有找到,则serached置为true,
* 同时该节点可以断定一定是新的待添加节点,比较内存地址大小决定往左往右递归待添加节点位置,
* 5、如果递归过程中发现节点已经扫描过了,则直接比较内存地址大小决定继续往哪一边递归,直到node == null时,退出循环添加新节点
* <p>
* 第三步在扫描之前再按照比较逻辑比较一次,避免直接进行下一步的扫描,提高了效率
*
* @param key
* @param value
* @return
*/
@Override
public V put(K key, V value) {
resize();
int index = index(key);
// 取出index位置的红黑树根节点
Node<K, V> root = table[index];
if (root == null) {
root = new Node<>(key, value, null);
table[index] = root;
size++;
fixAfterPut(root);
return null;
}
// 添加新的节点到红黑树上面
Node<K, V> parent = root;
Node<K, V> node = root;
int cmp = 0;
K k1 = key;
int h1 = hash(k1);
Node<K, V> result = null;
boolean searched = false; // 是否已经搜索过这个key
do {
parent = node;
K k2 = node.key;
int h2 = node.hash;
if (h1 > h2) {
cmp = 1;
} else if (h1 < h2) {
cmp = -1;
} else if (Objects.equals(k1, k2)) {
cmp = 0;
} else if (k1 != null && k2 != null
&& k1 instanceof Comparable
&& k1.getClass() == k2.getClass()
&& (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
} else if (searched) {
// 已经扫描了
cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
} else {
// searched == false; 还没有扫描,然后再根据内存地址大小决定左右
if ((node.left != null && (result = node(node.left, k1)) != null)
|| (node.right != null && (result = node(node.right, k1)) != null)) {
// 已经存在这个key
node = result;
cmp = 0;
} else {
// 扫描完成后也没有找到,故必然是添加新节点
searched = true;
cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
}
}
/*
* 只要cmp != 0就一直递归下去直到 node == null,然后根据cmp的大小(有内存地址大小决定)决定向左向右添加新节点
*/
if (cmp > 0) {
node = node.right;
} else if (cmp < 0) {
node = node.left;
} else {
// 相等执行覆盖操作
V oldValue = node.value;
node.key = key;
node.value = value;
node.hash = h1;
return oldValue;
}
} while (node != null);
// 循环结束后执行添加操作
Node<K, V> newNode = new Node<>(key, value, parent);
if (cmp > 0) {
parent.right = newNode;
} else {
parent.left = newNode;
}
size++;
// 新添加节点之后的处理(保证红黑树的性质)
fixAfterPut(newNode);
return null;
}
/**
* 获取指定key值对应的value
*
* @param key
* @return
*/
@Override
public V get(K key) {
Node<K, V> node = node(key);
return node != null ? node.value : null;
}
/**
* 删除指定key值对应value
*
* @param key
* @return
*/
@Override
public V remove(K key) {
return remove(node(key));
}
//先找到key值对应的节点然后再返回value
private V remove(Node<K, V> node) {
if (node == null) {
return null;
}
Node<K, V> willNode = node;
size--;
V oldValue = node.value;
if (node.hasTwoChildren()) {
// 度为2的节点
// 找到后继节点
Node<K, V> s = successor(node);
// 用后继节点的值覆盖度为2的节点的值
node.key = s.key;
node.value = s.value;
node.hash = s.hash;
// 删除后继节点
node = s;
}
// 删除node节点(node的度必然是1或者0)
Node<K, V> replacement = node.left != null ? node.left : node.right;
int index = index(node);
if (replacement != null) {
// node是度为1的节点
// 更改parent
replacement.parent = node.parent;
// 更改parent的left、right的指向
if (node.parent == null) {
// node是度为1的节点并且是根节点
table[index] = replacement;
} else if (node == node.parent.left) {
node.parent.left = replacement;
} else {
// node == node.parent.right
node.parent.right = replacement;
}
// 删除节点之后的处理
fixAfterRemove(replacement);
} else if (node.parent == null) {
// node是叶子节点并且是根节点
table[index] = null;
} else {
// node是叶子节点,但不是根节点
if (node == node.parent.left) {
node.parent.left = null;
} else {
// node == node.parent.right
node.parent.right = null;
}
// 删除节点之后的处理
fixAfterRemove(node);
}
// 交给子类去处理
afterRemove(willNode, node);
return oldValue;
}
@Override
public boolean containsKey(K key) {
return node(key) != null;
}
/**
* 按照索引遍历所有桶,桶内部采用层序遍历遍历红黑树节点
*
* @param value
* @return
*/
@Override
public boolean containsValue(V value) {
if (size == 0) {
return false;
}
Queue<Node<K, V>> queue = new LinkedList<>();
for (int i = 0; i < table.length; i++) {
if (table[i] == null) {
continue;
}
queue.offer(table[i]);
while (!queue.isEmpty()) {
Node<K, V> node = queue.poll();
if (Objects.equals(value, node.value)) {
return true;
}
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
return false;
}
/**
* 遍历(层序)
*
* @param visitor
*/
@Override
public void traversal(Visitor<K, V> visitor) {
if (size == 0 || visitor == null) {
return;
}
Queue<Node<K, V>> queue = new LinkedList<>();
for (Node<K, V> kvNode : table) {
if (kvNode == null) {
continue;
}
queue.offer(kvNode);
while (!queue.isEmpty()) {
Node<K, V> node = queue.poll();
if (visitor.visit(node.key, node.value)) {
return;
}
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
}
private Node<K, V> node(K key) {
//找到key值索引位置的红黑树根节点
Node<K, V> root = table[index(key)];
return root == null ? null : node(root, key);
}
/**
* 找到指定key位置的 node节点
* 寻找节点时的逻辑与添加时的逻辑大部分保持一致,所不同的是,添加时加入的比较内存地址大小决定向左向右的逻辑在查找node时不能再用了
*
* @param k1
* @return
*/
private Node<K, V> node(Node<K, V> node, K k1) {
int h1 = hash(k1);
//存储查找结果
Node<K, V> result = null;
int cmp = 0;
while (node != null) {
int h2 = node.hash;
K k2 = node.key;
/* 1.先通过哈希值决定往左还是往右查找 */
if (h1 > h2) {
node = node.right;
} else if (h1 < h2) {
node = node.left;
} /* 2.哈希值相同比较equals */ else if (Objects.equals(k1, k2)) {
return node;
} /* 3.如果哈希值相同但是不equals,按照自定义比较逻辑进行比较 */ else if (k1 != null && k2 != null
&& k1.getClass() == k2.getClass()
&& k1 instanceof Comparable
&& (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
node = cmp > 0 ? node.right : node.left;
} /* 4.如果哈希值相同equals不相等同时也不存在可比较性或者比较结果为0 扫描左右子树 */ else if (node.right != null && (result = node(node.right, k1)) != null) {
return result;
} else {
node = node.left;
}
}
return null;
}
/**
* 根据key生成对应的索引(在桶数组中的位置)
*/
private int index(K key) {
//按位与运算替代取模运算,生成<table.length的数的同时提高了效率
return hash(key) & (table.length - 1);
}
private int index(Node<K, V> node) {
return node.hash & (table.length - 1);
}
private int hash(K key) {
//如果key为null直接加在数组首位置
if (key == null) {
return 0;
}
int hash = key.hashCode();
//无符号右移16位后进行异或运算,综合利用元素的所有信息生成最终的hashcode
return hash ^ (hash >>> 16);
}
/**
* 扩容机制 新建一个数组,将原数组元素一个一个重新添加到新数组节点上
*/
private void resize() {
// 装填因子 <= 0.75 不扩容
if (size / table.length <= DEFAULT_LOAD_FACTOR) {
return;
}
/*
* 开始扩容
*/
Node<K, V>[] oldTable = table;
/*
* JDK8中是两倍扩容
* 当扩容为原来的两倍时,节点索引有两种情况:1.索引不变 2.索引index = index + OldLength
*/
table = new Node[oldTable.length << 1];
Queue<Node<K, V>> queue = new LinkedList<>();
for (Node<K, V> kvNode : oldTable) {
if (kvNode == null) {
continue;
}
queue.offer(kvNode);
while (!queue.isEmpty()) {
Node<K, V> node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
// 因为在挪动节点之前节点的信息被完全重置了,故该语句应该放在最后面
moveNode(node);
}
}
}
private void moveNode(Node<K, V> newNode) {
// 重置
newNode.parent = null;
newNode.left = null;
newNode.right = null;
newNode.color = RED;
int index = index(newNode);
// 取出index位置的红黑树根节点
Node<K, V> root = table[index];
if (root == null) {
root = newNode;
table[index] = root;
fixAfterPut(root);
return;
}
// 添加新的节点到红黑树上面
Node<K, V> parent = root;
Node<K, V> node = root;
int cmp = 0;
K k1 = newNode.key;
int h1 = newNode.hash;
do {
parent = node;
K k2 = node.key;
int h2 = node.hash;
if (h1 > h2) {
cmp = 1;
} else if (h1 < h2) {
cmp = -1;
} else if (k1 != null && k2 != null
&& k1 instanceof Comparable
&& k1.getClass() == k2.getClass()
&& (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
} else {
cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
}
if (cmp > 0) {
node = node.right;
} else if (cmp < 0) {
node = node.left;
}
} while (node != null);
// 看看插入到父节点的哪个位置
newNode.parent = parent;
if (cmp > 0) {
parent.right = newNode;
} else {
parent.left = newNode;
}
// 新添加节点之后的处理
fixAfterPut(newNode);
}
/**
* 以下为红黑树相关代码
*
* @param node
*/
private void fixAfterRemove(Node<K, V> node) {
// 如果删除的节点是红色
// 或者 用以取代删除节点的子节点是红色
if (isRed(node)) {
black(node);
return;
}
Node<K, V> parent = node.parent;
if (parent == null) {
return;
}
// 删除的是黑色叶子节点【下溢】
// 判断被删除的node是左还是右
boolean left = parent.left == null || node.isLeftChild();
Node<K, V> sibling = left ? parent.right : parent.left;
if (left) {
// 被删除的节点在左边,兄弟节点在右边
if (isRed(sibling)) {
// 兄弟节点是红色
black(sibling);
red(parent);
rotateLeft(parent);
// 更换兄弟
sibling = parent.right;
}
// 兄弟节点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
fixAfterRemove(parent);
}
} else {
// 兄弟节点至少有1个红色子节点,向兄弟节点借元素
// 兄弟节点的左边是黑色,兄弟要先旋转
if (isBlack(sibling.right)) {
rotateRight(sibling);
sibling = parent.right;
}
color(sibling, colorOf(parent));
black(sibling.right);
black(parent);
rotateLeft(parent);
}
} else {
// 被删除的节点在右边,兄弟节点在左边
if (isRed(sibling)) {
// 兄弟节点是红色
black(sibling);
red(parent);
rotateRight(parent);
// 更换兄弟
sibling = parent.left;
}
// 兄弟节点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
fixAfterRemove(parent);
}
} else {
// 兄弟节点至少有1个红色子节点,向兄弟节点借元素
// 兄弟节点的左边是黑色,兄弟要先旋转
if (isBlack(sibling.left)) {
rotateLeft(sibling);
sibling = parent.left;
}
color(sibling, colorOf(parent));
black(sibling.left);
black(parent);
rotateRight(parent);
}
}
}
//交由子类具体实现
protected void afterRemove(Node<K, V> willNode, Node<K, V> removedNode) {
}
private void fixAfterPut(Node<K, V> node) {
Node<K, V> parent = node.parent;
// 添加的是根节点 或者 上溢到达了根节点
if (parent == null) {
black(node);
return;
}
// 如果父节点是黑色,直接返回
if (isBlack(parent)) {
return;
}
// 叔父节点
Node<K, V> uncle = parent.sibling();
// 祖父节点
Node<K, V> grand = red(parent.parent);
if (isRed(uncle)) {
// 叔父节点是红色【B树节点上溢】
black(parent);
black(uncle);
// 把祖父节点当做是新添加的节点
fixAfterPut(grand);
return;
}
// 叔父节点不是红色
if (parent.isLeftChild()) {
// L
if (node.isLeftChild()) {
// LL
black(parent);
} else {
// LR
black(node);
rotateLeft(parent);
}
rotateRight(grand);
} else {
// R
if (node.isLeftChild()) {
// RL
black(node);
rotateRight(parent);
} else {
// RR
black(parent);
}
rotateLeft(grand);
}
}
private void rotateLeft(Node<K, V> grand) {
Node<K, V> parent = grand.right;
Node<K, V> child = parent.left;
grand.right = child;
parent.left = grand;
afterRotate(grand, parent, child);
}
private boolean isBlack(Node<K, V> node) {
return colorOf(node) == BLACK;
}
private boolean isRed(Node<K, V> node) {
return colorOf(node) == RED;
}
private boolean colorOf(Node<K, V> node) {
return node == null ? BLACK : node.color;
}
private Node<K, V> color(Node<K, V> node, boolean color) {
if (node == null) {
return node;
}
node.color = color;
return node;
}
private Node<K, V> red(Node<K, V> node) {
return color(node, RED);
}
private Node<K, V> black(Node<K, V> node) {
return color(node, BLACK);
}
private void rotateRight(Node<K, V> grand) {
Node<K, V> parent = grand.left;
Node<K, V> child = parent.right;
grand.left = child;
parent.right = grand;
afterRotate(grand, parent, child);
}
private void afterRotate(Node<K, V> grand, Node<K, V> parent, Node<K, V> child) {
// 让parent称为子树的根节点
parent.parent = grand.parent;
if (grand.isLeftChild()) {
grand.parent.left = parent;
} else if (grand.isRightChild()) {
grand.parent.right = parent;
} else {
// grand是root节点
table[index(grand)] = parent;
}
// 更新child的parent
if (child != null) {
child.parent = grand;
}
// 更新grand的parent
grand.parent = parent;
}
private Node<K, V> successor(Node<K, V> node) {
if (node == null) {
return null;
}
// 前驱节点在左子树当中(right.left.left.left....)
Node<K, V> p = node.right;
if (p != null) {
while (p.left != null) {
p = p.left;
}
return p;
}
// 从父节点、祖父节点中寻找前驱节点
while (node.parent != null && node == node.parent.right) {
node = node.parent;
}
return node.parent;
}
private void afterPut(Node<K, V> node) {
Node<K, V> parent = node.parent;
// 添加的是根节点 或者 上溢到达了根节点
if (parent == null) {
black(node);
return;
}
// 如果父节点是黑色,直接返回
if (isBlack(parent)) {
return;
}
// 叔父节点
Node<K, V> uncle = parent.sibling();
// 祖父节点
Node<K, V> grand = red(parent.parent);
if (isRed(uncle)) {
// 叔父节点是红色【B树节点上溢】
black(parent);
black(uncle);
// 把祖父节点当做是新添加的节点
afterPut(grand);
return;
}
// 叔父节点不是红色
if (parent.isLeftChild()) {
// L
if (node.isLeftChild()) {
// LL
black(parent);
} else {
// LR
black(node);
rotateLeft(parent);
}
rotateRight(grand);
} else {
// R
if (node.isLeftChild()) {
// RL
black(node);
rotateRight(parent);
} else {
// RR
black(parent);
}
rotateLeft(grand);
}
}
/**
* 定义红黑树节点
*
* @param <K>
* @param <V>
*/
protected static class Node<K, V> {
int hash;
K key;
V value;
boolean color = RED;
Node<K, V> left;
Node<K, V> right;
Node<K, V> parent;
public Node(K key, V value, Node<K, V> parent) {
this.key = key;
int hash = key == null ? 0 : key.hashCode();
this.hash = hash ^ (hash >>> 16);
this.value = value;
this.parent = parent;
}
public boolean hasTwoChildren() {
return left != null && right != null;
}
public boolean isLeftChild() {
return parent != null && this == parent.left;
}
public boolean isRightChild() {
return parent != null && this == parent.right;
}
public Node<K, V> sibling() {
if (isLeftChild()) {
return parent.right;
}
if (isRightChild()) {
return parent.left;
}
return null;
}
@Override
public String toString() {
return "Node_" + key + "_" + value;
}
}
}
转载:https://blog.csdn.net/m0_46550452/article/details/115479640
查看评论