下面编写的HashMap与JDK1.8源码的hHashMap来说:
忽略了链表转化为红黑树的情况。
public class HashMap<K,V> implements Map<K,V>{
//是所有节点数量总大小
private int size ;
private static final boolean RED = false ;
private static final boolean BLACK = true ;
//table数组存储每一个插入的红黑树的根节点Node<k,V>
private Node<K,V>[] table ;
//table数组一开始默认给一个初始长度(哈希表数组最好长度为 2的n次幂)
private static final int DEFAULT_CAPACITY = 1 << 4 ;
public HashMap() {
table = new Node[DEFAULT_CAPACITY] ;
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0 ;
}
@Override
public void clear() {
if (size == 0) {
return;//如果哈希表没有Node节点 那么直接返回
}
for(int i = 0 ; i<table.length ;i++) {
table[i] = null ;
}
size = 0 ;
}
/**
* 添加操作
* @param key
* @param value
* @return 返回值为被覆盖的value值
*/
@Override
public V put(K key, V value) {
resize() ;
int index = index(key) ;
//取出index 位置的红黑树根节点
Node<K,V> root = table[index] ;
boolean searched = false ;
if (root == null) {
root = new Node(key,value, root.hash, null) ;
table[index] = root ;
size ++ ;
afterPut(root);
return null ;
}
//2.说明添加的不是树中第一个元素
//2.1 确定根节点 root
Node<K,V> node = root ;
//2.2 假设出待插入节点对应的父节点的值
Node<K,V> parent = null ;
//2.3 记录最后一次比较器返回的值 来确定待插入节点插入到哪个方向
int cmp = 0 ;
K k1 = key ;
int h1 = key == null ? 0 : key.hashCode() ;//找出传入的key对应的hash值
//2.4 循环寻找待插入节点需要插入到的位置
do {
//记录cmp
cmp = compare(node.key,key,h1,node.hash) ;
//记录parent
parent = node ;
K k2 = node.key ;
int h2 = node.hash ;
Node<K,V> result = null ;
//和node方法思路基本一致 所以详细注释见node方法
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.getClass() == k2.getClass()
&& k1 instanceof Comparable
&& (cmp = ((Comparable)k1).compareTo(k2))!=0) {
//排除compareTo方法返回结果为0的情况 因为为0也并不代表两个对象是相同的
} else if (!searched) {//searched = false
//先扫描红黑树 如果不存在待添加的key 那么再进行使用内存地址比较!
if ((node.left != null && (result = node(node.left,k1))!=null)
||(node.right != null && (result = node(node.right,k1))!=null)) {
//说明已经存在待添加的key,此时result为待添加的节点在红黑树中所找到的节点 。node为根节点
//要新赋值node节点 重复利用之后的代码
node = result ;
cmp = 0 ;
} else { //不存在这个key
searched = true ;
//说明不存在待添加节点对应的key值 那么只可以使用内存地址进行比较添加
cmp = System.identityHashCode(k1) - System.identityHashCode(k2) ;
}
} else {
//searched = true
cmp = System.identityHashCode(k1) - System.identityHashCode(k2) ;
}
if (cmp > 0) {
//说明已存在节点元素值大,那么插到左边
node = node.right ;
} else if (cmp < 0) {
node = node.left ;
} else {
//cmp == 0 如果自定义比较器比较出相等时,我们这里的处理方式是覆盖值操作
node.key = key ;
V oldValue = node.value ;
node.value = value ;
return oldValue ;//返回的是被覆盖的value值
}
} while (node != null) ;
//3.退出循环之后,根据记录的parent值 进行创建待插入节点对应的Node对象
Node<K,V> newNode = new Node<>(key,value,h1,parent) ;
//4.根据记录的cmp值和parent值,进行确定把待插入节点插入到哪一个位置
if (cmp > 0) {
parent.left = newNode ;
} else {
//cmp < 0 的情况,cmp == 0 直接就返回了已经
parent.right = newNode ;
}
size ++ ;
//添加node节点后 进行的调整
afterPut(node); ;
return null ;
}
/**
* 获取对应key的value
* @param key 传入的key键值
* @return 返回key对应的value值
*/
@Override
public V get(K key) {
Node<K,V> node = node(key) ;
return node != null ? node.value : null ;
}
@Override
public V remove(K key) {
return remove(node(key));
}
@Override
public boolean containsKey(K key) {
return node(key) != null ;
}
/**
* 对于value的值不可以直接查询到 需要遍历所有节点
* @param value
* @return
*/
@Override
public boolean containsValue(V value) {
if (size == 0) {//如果没有节点 那么返回false
return false ;
}
Queue<Node<K,V>> queue = new LinkedList<>() ;
//这里的层序遍历和之前不同 需要遍历多个红黑树 所以使用for循环
for (int i = 0 ;i< table.length ;i++) {
if (table[i] == null) {
return false ;
}
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.left != null) {
queue.offer(node.right) ;
}
}
}
return false;
}
@Override
public void traversal(Visitor<K, V> visitor) {
if (size == 0 || visitor == null) {
return;
}
Queue<Node<K,V>> queue = new LinkedList<>() ;
for (int i = 0;i < table.length ;i++) {
if (table[i] == null) {
return;
}
queue.offer(table[i]) ;
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 void resize() {
//说明装填因子 <= 0.75 无需扩容 直接返回
if (size / table.length <= DEFAULT_CAPACITY) {
return;
}
// >0.75 扩容到原来的两倍
Node<K,V> []oldTable = table ;
table = new Node[oldTable.length >> 1] ;
Queue<Node<K,V>> queue = new LinkedList<>() ;
//把原来table数组上的key-value键值对每一个节点都进行转移到新数组
/**
* 当扩容为原来容量的2倍时,节点的索引有2种情况:
* 1.保持不变
* 2.要么就是index = index + 旧容量
*/
for (int i = 0 ; i< oldTable.length ;i++) {
if (oldTable[i] == null) {
return;
}
queue.offer(oldTable[i]) ;
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 ;
int index = index(newNode.key) ;
//取出index 位置的红黑树根节点
Node<K,V> root = table[index] ;
if (root == null) {//说明该桶索引位置还没有节点插入
root = newNode ;//所以直接插给root
table[index] = root ;
afterPut(root);
return;
}
//2.说明添加的不是树中第一个元素
//2.1 确定根节点 root
Node<K,V> node = root ;
//2.2 假设出待插入节点对应的父节点的值
Node<K,V> parent = null ;
//2.3 记录最后一次比较器返回的值 来确定待插入节点插入到哪个方向
int cmp = 0 ;
K k1 = newNode.key ;
int h1 = newNode.hash ;//找出传入的key对应的hash值
//2.4 循环寻找待插入节点需要插入到的位置
do {
//记录parent
parent = node ;
K k2 = node.key ;
int h2 = node.hash ;
Node<K,V> result = null ;
//和node方法思路基本一致 所以详细注释见node方法
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 {
//searched = true
cmp = System.identityHashCode(k1) - System.identityHashCode(k2) ;
}
if (cmp > 0) {
//说明已存在节点元素值大,那么插到左边
node = node.right ;
} else if (cmp < 0) {
node = node.left ;
}
} while (node != null) ;
//4.根据记录的cmp值和parent值,进行确定把待插入节点插入到哪一个位置
if (cmp > 0) {
parent.left = newNode ;
} else {
//cmp < 0 的情况,cmp == 0 直接就返回了已经
parent.right = newNode ;
}
newNode.parent = parent ;
//添加node节点后 进行的调整
afterPut(node);
}
private V remove(Node<K,V> node) {
if (node == null) {
return null;
}
V oldValue = node.value ;
size -- ;
//(1)删除的是度为2的节点
if (node.hasTwoChildren()) {
//找到该节点对应的前驱或后继节点
Node<K,V> s = succeed(node) ;//找到后继节点
//使用后继节点的值进行覆盖度为2的节点的值
node.key = s.key ;
node.value = s.value ;
//把待删除的节点进行赋值为前驱或后继节点,使用后面的逻辑进行删除前驱后继节点
node = s ;
}
//必须搞出一个replacement进行记录删除的是根节点左子树还是右子树的节点
Node<K,V> replacement = node.left != null ? node.left : node.right ;
int index = index(node.key) ;
//(2)删除的是度为1的节点
if (replacement != null) {
//统一进行更新删除节点度为1的操作之后的parent指向值
replacement.parent = node.parent ;
//2.1 如果是度为1的节点并且是root根节点
if (node.parent == null) {
table[index] = replacement ;
} else if (node == node.parent.left) {
//2.2 如果是左子树上的度为1的节点
node.parent.left = replacement ;
} else if (node == node.parent.right) {
//2.3 如果是右子树上的度为1的节点
node.parent.right = replacement ;
}
//真正被删除的是度为1或2的前驱或后继节点
afterRemove(node,replacement) ;
} else if (node.parent == null) {// (3) 如果删除的是叶子节点并且为root根节点
table[index] = null ;
} else {//(4) 如果删除的是叶子节点但是不为root根节点
if (node == node.parent.right) {
//4.1 如果是左子树上的度为0的节点
node.parent.right = null ;
} else {//node = node.parent.left
//4.2 如果是右子树上的度为0的节点
node.parent.left = null ;
}
}
return oldValue ;
}
/**
* 找到后继节点
* @param node
* @return
*/
private Node<K,V> succeed(Node<K,V> node) {
if (node == null) {
return null ;
}
//1,
Node<K,V> p = node ;
if (p.right != null) {
p = p.right ;
while (p.left != null) {
p = p.left ;
}
return p ;
}
//p.right == null
//目的是找node节点的后继节点,所以要找第一个比它大的
//所以在父节点不为空 并且一直在变化的父节点的右子树 、
// 如果它发现在左子树后,那么就要退出循环,说明找到了第一个比它大的节点
if (p.parent != null && p == p.parent.right) {
p = p.parent ;
}
//p.parent == null
//p == p.parent.left
return p.parent ;
}
/**
* HashMap中的key是不一定 也不太可能具备可比较性的
* 所以使用hash值去进行决定出加到哪个位置
* @param k1 键值1
* @param k2 键值2
* @param h1 k1对应的hash值
* @param h2 k2对应的hash值
* @return
*/
private int compare(K k1,K k2,int h1,int h2) {
//比较哈希值
int result = h1 - h2 ;
if (result != 0) {
return result ;
}
// result == 0 说明哈希值相等 但是还要进行比较是否equals
boolean equals = Objects.equals(k1, k2);//已经判断出k1==null && k2 == null的情况了。
if (equals) {
//说明k1,k2equals
return 0 ;
}
//说明只是哈希值相等 但是不equals
//但是总要分辨出谁大谁小 所以比较类名
if (k1 != null && k2 != null) {
String k1Cls = k1.getClass().getName() ;
String k2Cls = k2.getClass().getName() ;
result = k1Cls.compareTo(k2Cls) ;
if (result != 0) {
return result ;
}
//result == 0 说明是同一种类型并且具有可比较性
if (k1 instanceof Comparable) {
return ((Comparable)k1).compareTo(k2) ;
}
}
//说明是同一种类型 哈希值相等 但是不具备可比较性
//k1 != null && k2 == null
//k1 == null && k2 != null
//此时只可以进行两个key键值内存地址的比较
return System.identityHashCode(k1) - System.identityHashCode(k2) ;
}
private void afterRemove(Node<K,V> node, Node<K,V> replacement) {
//1。如果删除的节点是红色RED,删除之后无需处理
if (isRed(node)) return;
//2.用以取代被删除的node的子节点是RED时
if (isRed(replacement)) {
black(replacement) ;
return;
}
Node<K,V> parent = node.parent ;
//删除的是根节点
if (parent == null) return;
//3.删除的是黑色叶子节点
//判断被删除的node是左还有右:
//parent.left == null说明被删除的node节点是parent的左子树,
// 那么被删除的node节点的兄弟节点即是parent的右子树,反之同理即可
boolean left = parent.left == null ;
Node<K,V> sibling = left ? parent.right : parent.left ;
if (left) {//3.1被删除的节点在左边,那么兄弟节点sibling在右边
if (isRed(sibling)) {
//3.2.1删除的是黑色叶子节点并且sibling是红色,那么转换为sibling为黑色
black(sibling) ;
red(parent) ;
rotateRight(parent) ;
//更换兄弟
sibling = parent.right ;
}
//3.2.2删除的是黑色叶子节点并且sibling为BLACK
//3.2.2.1 兄弟节点没有一个是红色子节点,父节点要下根兄弟节点合并
if (isBlack(sibling.right) && isBlack(sibling.left)) {
//记录一下是否父节点也会下溢
boolean parentBlack = isBlack(parent) ;
black(parent) ;
red(sibling) ;
if (parentBlack) {
afterRemove(parent,null) ;
}
} else { //3.2.2.2 兄弟节点至少有1个红色子节点,被删除节点向兄弟节点借用元素
//兄弟节点的左边是黑色,说明右边是红色子节点
if (isBlack(sibling.right)) {
rotateLeft(sibling) ;
sibling = parent.right ;//旋转后要更新兄弟节点
color(sibling,colorOf(parent)) ;
black(sibling.right) ;
black(parent) ;
}
//兄弟节点的左边是红色,说明左边是红色子节点
color(sibling,colorOf(parent)) ;
black(sibling.right) ;
black(parent) ;
rotateRight(parent);
}
} else {//3.2被删除的节点在右边,那么兄弟节点sibling在左边
if (isRed(sibling)) {
//3.2.1删除的是黑色叶子节点并且sibling是红色,那么转换为sibling为黑色
black(sibling) ;
red(parent) ;
rotateRight(parent) ;
//更换兄弟
sibling = parent.left ;
}
//3.2.2删除的是黑色叶子节点并且sibling为BLACK
//3.2.2.1 兄弟节点没有一个是红色子节点,父节点要下根兄弟节点合并
if (isBlack(sibling.left) && isBlack(sibling.right)) {
//记录一下是否父节点也会下溢
boolean parentBlack = isBlack(parent) ;
black(parent) ;
red(sibling) ;
if (parentBlack) {
afterRemove(parent,null) ;
}
} else { //3.2.2.2 兄弟节点至少有1个红色子节点,被删除节点向兄弟节点借用元素
//兄弟节点的左边是黑色,说明右边是红色子节点
if (isBlack(sibling.left)) {
rotateLeft(sibling) ;
sibling = parent.left ;//旋转后要更新兄弟节点
color(sibling,colorOf(parent)) ;
black(sibling.left) ;
black(parent) ;
}
//兄弟节点的左边是红色,说明左边是红色子节点
color(sibling,colorOf(parent)) ;
black(sibling.left) ;
black(parent) ;
rotateRight(parent);
}
}
}
/**
* 添加操作可能毁坏红黑树的性质
* afterPut修复红黑树的性质
* @param node
*/
private void afterPut(Node<K, V> node) {
Node<K,V> parent = node.parent ;
//1.情况一中的四种添加位置,是无需进行修复处理红黑树的。
//1.1添加的是根节点
if (parent == null) {
black(node) ;//根节点直接染成黑色
return;
}
//1.2如果父节点是黑色,直接返回
if (isBlack(parent)) return ;
//2.情况二中的八种添加位置,是需要进行修复处理红黑树的。
//2.1拿到uncle节点进行判断 uncle节点:叔父节点即是父节点的兄弟节点
Node<K,V> uncle = parent.sibling() ;
//2.2祖父节点
Node<K,V> grand = parent.parent ;
//2.3 叔父节点为RED,只需进行染色 然后进行递归grand作为新添加的节点进行处理
if (isRed(uncle)) {
black(parent) ;
black(uncle) ;
//把祖父节点当作是新添加的节点进行处理
red(grand) ;
afterPut(grand); ;
return;
}
//2.4 叔父节点不为RED,需要进行旋转处理
if (parent.isLeftChild()) { //parent是grand的左子树节点 L
if (node.isLeftChild()) { //node是parent的左子树节点 LL
//LL和RR为一类,parent染成BLack,grand染成RED
black(parent) ;
red(grand) ;
rotateRight(grand);
} else { //LR
//LR 和 RL为一类,新添加的节点染成Black,grand染成RED
black(node) ;
red(grand) ;
rotateLeft(parent) ;
rotateRight(grand) ;
}
} else { //R
if (node.isLeftChild()) { //RL
//LR 和 RL为一类,新添加的节点染成Black,grand染成RED
black(node) ;
red(grand) ;
rotateRight(parent); ;
rotateLeft(grand); ;
} else {//RR
//LL和RR为一类,parent染成BLack,grand染成RED
black(parent) ;
red(grand) ;
rotateLeft(grand);
}
}
}
/**
* 对传入的节点进行染色处理
* @param node 想要染色的节点
* @param color 把该节点node染成color颜色
* @return 返回染色后的节点
*/
private Node<K,V> color(Node<K,V> node, boolean color) {
if (node == null) {
return node ;
}
node.color = color ;
return node ;
}
/**
* 把该node节点染成RED
*/
private Node<K,V> red(Node<K,V> node) {
return color(node,RED) ;
}
/**
* 把该node节点染成BLACK
*/
private Node<K,V> black(Node<K,V> node) {
return color(node,BLACK) ;
}
/**
* 传入一个node节点,进行返回该节点是什么颜色
*/
private boolean colorOf(Node<K,V> node) {
//如果node为null,即是null节点 即是BLACK
// 【对null节点进行特殊处理的原因是:我们一开始并没有对null节点的颜色进行设置】
//如果不为空,那么返回node实际的颜色即可
return node == null ? BLACK : node.color ;
}
/**
* 判断该node节点颜色color是否为BLACK
*/
private boolean isBlack(Node<K,V> node) {
return colorOf(node) == BLACK ;
}
/**
* 判断该node节点颜色color是否为RED
*/
private boolean isRed(Node<K,V> node) {
return colorOf(node) == RED ;
}
/**
* 左旋转
* @param grand 想要进行旋转的节点,传过来的是什么,什么节点就进行左旋转
*/
private void rotateLeft(Node<K,V> grand) {
//进行左旋转,说明是RR,可以确定parent
Node<K,V> parent = grand.right ;
//child即是T1
Node<K,V> child = parent.left ;
grand.right = child; //1.
parent.left = grand ; //2.
//执行旋转之后的逻辑
afterRotate(grand,parent,child);
}
/**
* 右旋转
* @param grand 想要进行旋转的节点,传过来的是什么,什么节点就进行右旋转
*/
private void rotateRight(Node<K,V> grand) {
//1.进行右旋转,说明是LL,那么可以确定parent
Node<K,V> parent = grand.left ;
//2.找出T2
Node<K,V> child = parent.right ;
//3.对g执行右旋转
grand.left = child ; //3.1
parent.right = grand ;//3.2
//执行旋转之后的逻辑
afterRotate(grand,parent,child);
}
/**
* 旋转之后统一要做的逻辑
* @param grand (最低的失去平衡的节点)
* @param parent (parent是grand的子节点)
* @param child 旋转过程中父节点改变的节点(T1或T2)
*/
private void afterRotate(Node<K,V> grand, Node<K,V> parent, Node<K,V> child) {
int index = index(grand.key) ;
//3.让parent成为这颗子树的根节点
//3.1parent先连上grand的父节点
parent.parent = grand.parent ;
//3.2 grand再去连上parent
if (grand.isLeftChild()) {
//如果grand是父节点的左子树
grand.parent.left = parent ;
} else if (grand.isRightChild()) {
//如果grand是父节点的右子树
grand.parent.right = parent ;
} else {//既不是父节点的左子树又不是右子树,那么说明grand即是root根节点
table[index] = parent ;
}
//4.更新child(T1)的parent
if (child != null) {
child.parent = grand ;
}
//5.更新grand的parent
grand.parent = parent ;
}
/**
* 根据key找到Node节点
*/
public Node<K,V> node(K key) {
Node<K,V> root = table[index(key)] ;
return root == null ? null : node(root,key) ;//递归调用
}
/**
* 根据key找到Node节点
*/
private Node<K,V> node(Node<K,V> node,K k1) {
int index = index(k1) ;
int h1 = k1 == null ? 0 : k1.hashCode() ;
Node<K,V> result = null ;
int cmp = 0 ;
while (node != null) {
K k2 = node.key ;
int h2 = node.hash ;
//先比较哈希值
if (h1 > h2) {
node = node.right ;
} else if (h1 < h2) {
node = node.left ;
} else if (Objects.equals(k1,k2)) {
//哈希值相等 并且equals相等 并且已经判断过内存地址相同的情况了 之后都是内存地址不相等的情况
return node ;//那么找到了
} else if (k1 != null && k2 != null
&& k1.getClass() == k2.getClass()
&& k1 instanceof Comparable
&& (cmp = ((Comparable)k1).compareTo(k2))!=0) { //哈希值相等 但是equals不相等 但是类相同 并且具备可比较性
if (cmp > 0) {
node = node.right ;
} else if (cmp < 0) {
node = node.left;
}
// } else { //cmp == 0 并不代表两个对象真的就是相等的,所以要执行后面遍历搜索的代码
// return node ;
// }
} else if (node.right != null && (result = node(node.right,k1))!=null) {
//哈希值相等 但是不具备可比较性 也不equals。之后进行递归 如果向右可以找到的话
return result;
// } else if (node.left != null && (result = node(node.left,k1)) !=null) {
// //哈希值相等 但是不具备可比较性 也不equals。之后进行递归 如果向左可以找到的话
// return result ;
// } else {//找不到
// return null ;
// }
} else {//只能往左边,减少递归
node = node.left ;
}
}
return null ;//找不到
}
/**
* 根据key生成对应的索引(在桶数组的位置)
*/
private int index(K key) {
//1.使用自身定义的hashCode方法获取哈希值
int hash = key.hashCode();
//2.自身hashCode方法或许获取的哈希值是不稳定的 所以jdk再进行帮忙均匀计算一下
hash = hash ^ (hash >>> 16) ;
//3.计算出索引值并且返回
return hash & (table.length - 1) ;
}
private static class Node<K,V> {
K key ;
V value ;
boolean color = RED;
int hash ;
Node<K,V> left ;//左子节点
Node<K,V> right ;//右子节点
Node<K,V> parent ;//父节点
int height ;
public Node(K key, V value , int hash, Node<K,V> parent) {
this.key = key ;
this.value = value ;
this.hash = key == null ? 0 : key.hashCode() ;
this.parent = parent ;//除了根节点,其余节点都具有父节点,所以要在构造方法传入
}
//判断是否为叶子节点
public boolean isLeaf() {
return left == null && right == null ;
}
//判断是否具有两个子节点
public boolean hasTwoChildren() {
return left != null && right != null ;
}
//说明是parent的左子树
public boolean isLeftChild() {
return parent != null && this == parent.left ;
}
//说明是parent的右子树
public boolean isRightChild() {
return parent != null && this == parent.right ;
}
/**
* 找出兄弟节点
* @return
*/
public Node<K,V> sibling() {
if (isLeftChild()) {
//说明该节点是父节点的左子树
return parent.right ;//说明该节点的兄弟节点即是父节点的右子树
}
if (isRightChild()) {
//说明该节点是父节点的右子树
return parent.left ;//说明该节点的兄弟节点即是父节点的左子树
}
//说明该节点既不是父节点的左子树 也不是父节点的右子树
//那么说明该节点没有父节点,那么就没有兄弟节点
return null ;
}
}
}