哈希表

306 阅读16分钟

0. 哈希表概述

哈希表也叫做散列表( hash 有“剁碎”的意思),hash table
哈希表可以高效的处理数据。是根据关键码值(Key value)而直接进行访问的数据结构。
也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。

这个映射函数叫做散列函数,存放记录的数组叫做散列表。
举个例子:
存储三个数据:
(“Zhangsan” , 178);
(“Lisi”, 173);
(“Wangwu”, 175);

其处理过程类似于下图图一所示:

image.png

key 值传入哈希函数,经过一定的运算处理,可以得到对应 table 数组的索引值,然后将 value 存入数组。
即:Zhangsan 经过哈希函数的处理,得到索引为 11,将 Zhangsan 对应的值 178 存入数组索引为 11 的地方。... 注:哈希表除了将 value 178 存入该位置,其key也是连同一起存入的。

哈希函数的复杂度为 O(1), table 为一个数组。

添加、搜索、删除的流程都是类似的:

  1. 利用哈希函数生成 key 对应的 index【O(1)】
  2. 根据 index 操作定位数组元素【O(1)】

哈希表是【空间换时间】的典型应用。
哈希表内部的数组元素,很多地方也叫 Bucket(桶),整个数组叫 Buckets 或者 Bucket Array。

1. 哈希冲突

哈希冲突也叫做哈希碰撞。当不同的 key,经过哈希函数计算出相同的结果,即 key1 ≠ key2,hash(key1) = hash(key2)。 如下图二中,Zhangsan 和 Wangwu,两个不同的 key,但经过哈希函数计算后,得到的索引值相同。

image.png

当不同的 key,经过哈希函数计算出相同的结果时,如果直接将 key 对应的值存储在数组相同的索引位置,将会出现严重的问题。因此必须解决哈希冲突。

解决哈希冲突的常见方法:
1)开放定址法(Open Addressing)
按照一定规则向其他地址探测,直到遇到空桶。
开放寻址法:位置被占了,通过一定的规则,另外再找个位置。
可以使用线性探测法:当前位置被占用了,继续看该位置的后一个位置是否可用,针对上图的话也就是 index = 3 的位置被占用了,就看 index = 4 的位置,如果没有被占用,那就放到这里。如果 index = 4 也的位置也被占用,就继续往下找,以此类推,直到找到空位置。

当然也可以使用平方探测法。

2)再哈希法(Re-Hashing)
设计多个哈希函数

3)链地址法(Separate Chaining)
通过链表(或者红黑树)将同一 index 的元素串起来。
开放定址法是在数组上另外找个新位置。对于拉链法,还是在该索引位置,采用链表(或者红黑树)。

如下图图3,11、21、31 要放在 1 这个索引位置上,11 先来,先存储。然后 21、31 也定位到这个索引,此时采用链表将 21 和 31 存储起来。这样对于table(本质是数组)同一个索引位置对应的值是一个链表。

当然,如果链表长度达到一定阈值后,链表就会转换成树结构(如图中索引为 63 的元素值),当然如果长度小于一定阈值后,就会还原成链表。这样可以解决链表过长导致的性能问题。

image.png

JDK1.8 的哈希冲突解决方案

JDK1.8 中的哈希表是使用链表+红黑树解决哈希冲突。

默认使用单向链表将元素串起来。

每次都是从头节点开始遍历;单向链表比双向链表少一个指针,可以节省内存空间。

在添加元素时,可能会由单向链表转为红黑树来存储元素。

比如当哈希表容量 ≥ 64 且 单向链表的节点数量大于 8 时。

当红黑树节点数量少到一定程度时,又会转为单向链表。

2. 哈希函数

良好的哈希函数:让哈希值更加均匀分布 → 减少哈希冲突次数 → 提升哈希表的性能。

哈希表中哈希函数的实现步骤大概如下

  1. 先生成 key 的哈希值(必须是整数);
  2. 再让 key 的哈希值跟数组的大小进行相关运算,生成一个索引值。
/**
 * 哈希函数计算出 key 对应的索引值
 * 1)生成 key 的哈希值(整数)
 * 2)将生成的哈希值同 数组大小进行运算,得到索引值
 */
public int hash(Object key) {
    return hash_code(key) % table.length;
}

为了提高效率,可以使用 & 位运算取代 % 运算【前提:将数组的长度设计为 2 的幂(2n)】

// 使用 & 位运算取代 % 运算【前提:将数组的长度设计为 2 的幂(2n)】
public int hash(Object key) {
    return hash_code(key) & table.length;
}

2.1 如何生成key的哈希值

key 的常见种类可能有:整数、浮点数、字符串、自定义对象。

不同种类的 key,哈希值的生成方式不一样,但目标是一致的:

  • 尽量让每个 key 的哈希值是唯一的;
  • 尽量让 key 的所有信息参与运算。

在 Java 中,HashMap 的 key 必须实现 hashCode、equals 方法,也允许 key 为 null。

接下来针对不同类型的 key,看看其 hashCode 的生成方式。

2.1.1 整数

整数值当做哈希值:
比如 10 的哈希值就是 10

public static int hashCode(int value) {
    return value;
}

Integer.hashCode(10); //10

2.1.2 浮点数

将存储的二进制格式转为整数值

public static int hashCode(float value) {
    return floatToIntBits(value);
}

Float.hashCode((10.01); //1092626678
    

2.1.3 Long 和 Double 的哈希值

java 中,哈希值是 int 类型,32位

">>>" 无符号右移运算; "^" 异或运算。

public static int hashCode(long value) {
    return (int)(value ^ (value >>> 32));
}

Long.hashCode(10101); //10101
public static int hashCode(double value) {
    long bits = doubleToLongBits(value);
    return (int)(bits ^ (bits >>> 32));
}

Double.hashCode(100100100.01); //1369301201

2.1.4 字符串的哈希值

我们先看看整数 1024 是怎么样计算出来的?
1024 = 1 * 103 + 0 * 102 + 2 * 101 + 4 * 100

字符串是由若干个字符组成的,比如字符串 lisi,由 l、i、s、i 四个字符组成(字符的本质就是一个整数)。因此,lisi 的哈希值可以表示为 l ∗ n3 + i ∗ n2 + s ∗ n1 + i ∗ n0,等价于 [(j ∗ n + a) ∗ n + c] ∗ n + k。

在 JDK 中,乘数 n 为 31,为什么使用 31?

31 是一个奇素数,JVM会将 31 * i 优化成 (i << 5) – i

同样的思路,字符串的哈希值

String str = "lisi";
int hashCode = 0;
int len = str.length();
for (int i = 0; i < len; i++) {
    char c = str.charAt(i);

//            hashCode = 31 * hashCode + c;

    // 31 * i 优化成 (i << 5) - i
    hashCode = ((hashCode << 5) - hashCode) + c;
}

System.out.println("自定义 hashCode 计算结果:" + hashCode); //3322003
System.out.println("jdk 内部 hashCode 结果:" + str.hashCode()); //3322003

2.1.5 自定义对象的哈希值

自定义对象的哈希值,默认和其内存地址相关。如自定义 Person 类:

/**
 * Person 类
 */
public class Person {
    private String name;  //姓名
    private int age;  //年龄
    private float height;  //身高

    public Person(String name, int age, float height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }
}

测试类中:
public static void main(String[] args) {
    Person p1 = new Person("Zhangsan", 18, 175);
    Person p2 = new Person("Zhangsan", 18, 175);

    System.out.println(p1.hashCode());
    System.out.println(p2.hashCode());
}

打印信息:

p1 哈希值:325040804
p2 哈希值:1173230247

如果不重写 hashCode() 方法,直接获取到的哈希值,和对象的内存相关,也就是说即便是信息完全相同,哈希值也不相同。

为了达到 person 完全相同时,哈希值也相同,则需要重写 hashCode() 方法

/**
 * Person 类
 */
public class Person {
    private String name;  //姓名
    private int age;  //年龄
    private float height;  //身高

    public Person(String name, int age, float height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    @Override
    public int hashCode() {
        int hashCode = (name != null ? name.hashCode() : 0);
        hashCode = hashCode * 31 + Integer.hashCode(age);
        hashCode = hashCode * 31 + Float.hashCode(height);
        return hashCode;
    }
}

public static void main(String[] args) {
    Person p1 = new Person("Zhangsan", 18, 175);
    Person p2 = new Person("Zhangsan", 18, 175);

    System.out.println("p1 哈希值:" + p1.hashCode());
    System.out.println("p2 哈希值:" + p2.hashCode());
}

打印信息:
p1 哈希值:777175490
p2 哈希值:777175490

之前提到:在 Java 中,HashMapkey 必须实现 hashCodeequals 方法。
那么为什么重写了 hashCode,还要实现 equals 方法呢?
不同的对象,哈希值可能相同,最终得到的索引值相同;不同的对象,哈希值可能不相同,但最终得到的索引值也可能相同。 所以,当自定义对象作为 key 时,索引值相同,需要遍历判断链表中节点存储的元素是否相同,即此时需要判断 key 是否相同(即 equals)。

如下图中,p1 和 p2 的各属性值均相同,按照我们上面重写的 hashCode 方法的规则,二者的哈希值相同,其计算的索引相同,这个时候即出现哈希冲突,当 p1 存储在索引为 1 之后,存储 p2 时,索引相同,需要比较 key,即 p1 和 p2 是否相同(equals),

image.png

重写 equals 方法

@Override
/**
 * 用来比较两个 person 对象是否相等
 */
public boolean equals(Object obj) {
    // 内存地址
    if (this == obj) return true;
    if (obj == null || obj.getClass() != getClass()) return false;
    // 比较成员变量
    Person person = (Person) obj;
    return (person.name == null ? name == null : person.name.equals(name)) 
            && person.age == age
            && person.height == height;
}

如果自定义对象要作为哈希表的 key,那么根据实际需求,当相关成员值相同即判定二者相同是,则 hashCodeequals 只需要对相应的成员进行运算判断即可,即 hashCode 方法中使用的成员和 equals 中使用的成员,必须相同。

我们前面提到过对于 jdk1.8 在添加元素时会出现单向链表和红黑树的转换,为了简便我们直接将哈希表数组的元素存放为红黑树的实现,数据元素存放红黑树的根节点,即存储的是节点,而非整个红黑树对象。

存储节点可以简化红黑树,比如里面的size属性可以删除

针对自定义对象作为 key 的小结

  • 自定义对象作为 key,最好同时重写 hashCodeequals 方法。

  • equals:用以判断 2 个 key 是否为同一个 key。

    1. 自反性:对于任何非 null 的 x,x.equals(x)必须返回true;
    2. 对称性:对于任何非 null 的 x、y,如果 y.equals(x) 返回 true,x.equals(y) 必须返回 true;
    3. 传递性:对于任何非 null 的 x、y、z,如果 x.equals(y)、y.equals(z) 返回 true,那么x.equals(z) 必须返回 true;
    4. 一致性:对于任何非 null 的 x、y,只要 equals 的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y) 就会一致地返回 true,或者一致地返回 false;
    5. 对于任何非 null 的 x,x.equals(null) 必须返回 false。
  • hashCode :必须保证 equals 为 true 的 2 个 key 的哈希值一样。

  • 反过来 hashCode 相等的 key,不一定 equals 为 true。

不重写 hashCode 方法只重写 equals 会有什么后果?
可能会导致 2 个 equals 为 true 的 key 同时存在哈希表中。

HashMap 存储的 key 不要求可比较性,所以针对 keycompare 不能使用之前红黑树的 compare 逻辑,所以需要重新实现 compare。 在 HashMap 的接口中,添加(put) 或者 查询 中针对 key 的查找比较,其判断逻辑大致如下:

  1. 比较两个 key 的 hash值大小,大于 、小于;
  2. 如果 hash 值相等时,判断两个 key 是否相同(equals);
  3. 如果 equlas 不相等,且两个 key 不为空,且可比较(实现了 Comparable)且两者是同一种类型且两者比较值不相等;
  4. 如果不满足3,则执行节点的左右子树进行扫描(遍历每个节点判断节点的key);
  5. 最后只能比较两个key的内存大小,

插入的时候,需要执行需要有第5点的处理逻辑,在查询的时候则不需要该逻辑(因为添加的时候已经给了规则,查询最后有遍历兜底)。

3. 代码

HashMap 节点类

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;
    }
}

是否包含 key

这个时候对 key 的比较和红黑树中的比较的区别

/**
 * 是否包含 key
 */
public boolean containsKey(K key) {
    return node(key) != null;
}


private Node<K, V> node(K key) {
    Node<K, V> root = table[index(key)];
    return root == null ? null : node(root, key);
}

/**
 * 查询 key 对应的节点
 * @param node table 中对应的index的红黑树的根节点
 * @param k1 查询的 key
 * @return
 */
private Node<K, V> node(Node<K, V> node, K k1) {
    // 查询 key 的哈希值
    int h1 = hash(k1);
    
    // 存储查找结果
    Node<K, V> result = null;
    int cmp = 0;
    while (node != null) {
        K k2 = node.key;
        int h2 = node.hash;
        // 1.先比较哈希值
        if (h1 > h2) {
            node = node.right;
        } else if (h1 < h2) {
            node = node.left;
            
        // 2.hash 值相等,比较equals
        } else if (Objects.equals(k1, k2)) {
            return node;
        
        // 3. equlas 不相等,且两个 key 不为空,且可比较(实现了 Comparable)且两者是同一种类型且两者比较值不相等
        } else if (k1 != null && k2 != null 
                    && k1 instanceof Comparable
                    && k1.getClass() == k2.getClass()
                    && (cmp = ((Comparable)k1).compareTo(k2)) != 0) {
            node = cmp > 0 ? node.right : node.left;
         
        // 4.节点的左右子树进行扫描
        // 4.1 节点右子树进行扫描
        } else if (node.right != null && (result = node(node.right, k1)) != null) { 
            return result;
            
        // 4.2 节点左子树进行扫描
        } else { // 只能往左边找
            node = node.left;
        }
    }
    return null;
}

完整的代码

/**
 * HashMap 实现类
 */
public class HashMap<K, V> {
    private static final boolean RED = false;
    private static final boolean BLACK = true;
    private int size; //哈希表所有节点值(每个索引对应的红黑树的所有节点之和)
    private Node<K, V>[] table;
    private static final int DEFAULT_CAPACITY = 1 << 4; //数组(table)默认容量
    private static final float DEFAULT_LOAD_FACTOR = 0.75f; //装填因子

    public HashMap() {
        table = new Node[DEFAULT_CAPACITY];
    }
    
    public int size() {
        return size;
    }

    /**
     * 是否为空
     */
    public boolean isEmpty() {
        return size == 0;
    }
    
    /**
     * 清空
     */
    public void clear() {
        if (size == 0) return;
        size = 0;
        for (int i = 0; i < table.length; i++) {
                table[i] = null;
        }
    }

    /**
     * 添加
     */
    public V put(K key, V value) {
        // 扩容判断
        resize();

        int index = index(key);
        // 取出index位置的红黑树根节点
        Node<K, V> root = table[index];
        if (root == null) {
            root = createNode(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 { // 不存在这个key
                    searched = true;
                    cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
                }
            }

            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 = createNode(key, value, parent);
        if (cmp > 0) {
            parent.right = newNode;
        } else {
            parent.left = newNode;
        }
        size++;

        // 新添加节点之后的处理
        fixAfterPut(newNode);
        return null;
    }

    public V get(K key) {
        Node<K, V> node = node(key);
        return node != null ? node.value : null;
    }

    public V remove(K key) {
        return remove(node(key));
    }

    /**
     * 是否包含 key
     */
    public boolean containsKey(K key) {
            return node(key) != null;
    }

    /**
     * 是否包含 value
     */
    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;
    }

    /**
     * 遍历访问所有节点
     */
    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) continue;

            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);
                }
            }
        }
    }

    protected Node<K, V> createNode(K key, V value, Node<K, V> parent) {
        return new Node<>(key, value, parent);
    }

    private void resize() {
        // 装填因子 <= 0.75
        if (size / table.length <= DEFAULT_LOAD_FACTOR) return;

        Node<K, V>[] oldTable = table;
        table = new Node[oldTable.length << 1];

        Queue<Node<K, V>> queue = new LinkedList<>();
        for (int i = 0; i < oldTable.length; i++) {
            if (oldTable[i] == null) continue;

            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;
        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;

            // 挪动的时候只需要判断是往左还是往右,根本不需要判断equals以及遍历(因为添加《put》的时候已经判断过了,这里只是扩容挪动而已),而且cmp也不可能等于0
            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);
    }

    protected 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;
    }

    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 Node<K, V> node(K key) {
        Node<K, V> root = table[index(key)];
        return root == null ? null : node(root, key);
    }

    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) {
            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)) {
                return node;
            } else if (k1 != null && k2 != null 
                    && k1 instanceof Comparable
                    && k1.getClass() == k2.getClass()
                    && (cmp = ((Comparable)k1).compareTo(k2)) != 0) {
                node = cmp > 0 ? node.right : node.left;
            } 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) {
        return hash(key) & (table.length - 1);
    }

    private int hash(K key) {
        if (key == null) return 0;
        int hash = key.hashCode();
        return hash ^ (hash >>> 16);
    }

    private int index(Node<K, V> node) {
        return node.hash & (table.length - 1);
    }

    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);
            }
        }
    }

    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 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> 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 boolean colorOf(Node<K, V> node) {
        return node == null ? BLACK : node.color;
    }

    private boolean isBlack(Node<K, V> node) {
        return colorOf(node) == BLACK;
    }

    private boolean isRed(Node<K, V> node) {
        return colorOf(node) == RED;
    }

    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;
        }
    }
}

4. LinkedHashMap

LinkedHashMapHashMap 的基础上维护元素的添加顺序,使得遍历的结果是遵从添加顺序的。

其顺序是通过双向链表记录每个添加节点

在通过 hashcode 得到 index 添加节点的时候,链表的每个节点需要记录节点的上一个节点(pre)和下一个节点(next);

注意:双向链表是针对所有索引位置的所有红黑树

如下图6,绿色虚线即为遍历的顺序,和添加的顺序一样。 image.png

因此 LinkedHashMap 的添加元素是,需要额外补充双向链表的代码逻辑,同时在输出元素时,也要补充双向链表删除元素的逻辑

注意:删除元素时,注意其节点类需要在 HashMap 节点类(上面代码中的 Node 类)的基础上,额外新增两个成员 prev 和 next,即:

public static class LinkedNode<K, V> extends Node<K, V> {
    LinkedNode<K, V> prev;
    LinkedNode<K, V> next;
    public LinkedNode(K key, V value, Node<K, V> parent) {
        super(key, value, parent);
    }
}

LinkedHashMap 的添加元素时,需要额外补充双向链表的代码逻辑,同时在输出元素时,也要补充双向链表删除元素的逻辑

注意:删除元素时,当删除度为2的节点node时,需要注意更换 node 与 前驱\后继节点 的连接位置。

image.png

LinkedHashMap 的代码,略...