算法筑基(六)HashMap

127 阅读2分钟

不知道有没有遇到过面试官让你手写HashMap的。虽然我写不出来,但是有关于hashmap的几处关键点,还是应该记录一下,起码做到心中有个大体的思路。

HashMap的数据结构

数组+链表

image.png

Node源码

//Node是单向链表,它实现了Map.Entry接口
static class Node<k,v> implements Map.Entry<k,v> {
    final int hash;
    final K key;
    V value;
    Node<k,v> next;
    //构造函数Hash值 键 值 下一个节点
    Node(int hash, K key, V value, Node<k,v> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
 
    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + = + value; }
 
    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }
 
    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }
    //判断两个node是否相等,若key和value都相等,返回true。可以与自身比较为true
    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }

hash函数

为什么要先介绍hash函数,因为hash函数决定了key存取的位置。比如我们要调用map.put("a","value1"),那我们要把“a”放在数组的哪个位置呢。通过hash函数可以帮我们计算一个散列的值(-2147483648到2147483648之间),但是我们当前的数组只有16个,越界了啊。所以需要通过对hash值取余,hashmao中的取余操作:

hash & (table.length-1)

这样做会比hash % (table.length)更加高效,但是取余只有低位参与运算,就容易产生碰撞,所以这时候就需要进行扰动,减少碰撞,使key的分布更加均匀。所以hashmap中的hash算法是这样的:

static final int hash(Object key) {//hash算法
     int h;
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }

首先int h = key.hashCode() h是一个4字节32位的数,接着是异或上h >>> 16 最后得到了新的hash值。

h是个32位的数,那么它右移16位后,高位(前16位)位0,而x ^ 0 = x。所以整个上面一句话的作用就是保留高位,低位和高位异或,这样子能达到的目标就是扰乱了低位,且低位具有高位的一部分属性。

扩容

当hashmap的容量超过 扩容阈值 = table数组容量 * 负载因子(默认是0.75)的时候开始扩容,table数组变成原来的2倍,旧k-v重新散列在新数组中。至于扩容后的容量为什么是原来的2倍,这里面有解释,先mark一下blog.csdn.net/weixin_4451…

红黑树

无论hash函数多么均匀,但是还是会产生碰撞,所以当hash冲突的时候,需要在链表后面追加元素,当链表的长度超过8个的时候,为了提高搜索效率会将链表转变位红黑树,一种近似平衡的二叉树。这时候hashmap的结构是这样的。

0b061825c0a6ddb9a27bdc5426589e34.png

参考链接:

juejin.cn/post/684490…

zhuanlan.zhihu.com/p/367326239

juejin.cn/post/703376…