HashMap

作者: Zeppelin421 | 来源:发表于2022-04-13 09:13 被阅读0次

    核心成员变量

    JDK7

    • Entry[] table。Entry存储了HashMap的真正数据
    • size大小,代表HashMap内存储了多少个键值对
    • capacity容量。实际上HashMap中没有一个成员叫capacity,它是作为table这个数组的大小而隐式存在
    • threshold阈值和loadFactor(默认0.75f)装载因子。threshold是通过capacity*loadFactor得到的。当size超过threshold时(刚好相等时不会扩容),HashMap扩容会再次计算每个元素的hash位置
    • entrySet、keySet和values这三个都是一种试图,真正地数据都来自table

    JDK8

    • \color{red}{Node[]} table。这个Node类型的数组存储了HashMap的真正数据 Node<K,V> implements Map.Entry<K,V>
    • size大小,代表HashMap内存储了多少个键值对
    • capacity容量。实际上HashMap中没有一个成员叫capacity,它是作为table这个数组的大小而隐式存在
    • threshold阈值和loadFactor(默认0.75f)装载因子。threshold是通过capacity*loadFactor得到的。当size超过threshold时(刚好相等时不会扩容),HashMap会扩容(\color{red}{hash计算比之前有改进}
    • entrySet、keySet和values这三个都是一种试图,真正地数据都来自table
    • \color{red}{TREEIFY_THRESHOLD 转换成红黑数的临界值,默认8}
    • \color{red}{UNTREEIFY_THRESHOLD 红黑树转换成链表的临界值,默认6}
    • \color{red}{MIN_TREEIFY_CAPACITY 最小数形化阈值,默认64}

    JDK8阈值变化原因

    到8转为红黑树

    • TreeNode(红黑树中)占用空间是普通Node(链表中)的两倍,为了时间和空间的权衡
    • 节点的分布频率会遵循泊松分布,链表长度达到8个元素的概率为0.00000006

    到6转为链表

    • 若是7,则当极端情况下(频繁插入和删除的都是同一个hash桶)对一个链表长度为8的hash桶进行频繁的删除和插入,同样也会导致频繁的树化<=>非树化

    源码分析

    put过程

    JDK7
    JDK8

    put操作

    // JDK7
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {  // 表空进行初始化
            inflateTable(threshold);
        }
        if (key == null) // 键空
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length); // 计算bucket的位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            // hash相同 equals 比较是真则替换返回旧值
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
    
        modCount++;
        addEntry(hash, key, value, i);  // 添加数据 这里包含hash冲突的处理
        return null;
    }
    
    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            // 扩容操作,将数据元素重新计算位置后放入newTable中,Hash位置会重新计算
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
    
        createEntry(hash, key, value, bucketIndex);
    }
    
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        // 根据头结点构建
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
    
    // JDK8
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    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)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == 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))))
                e = p;
            else if (p instanceof TreeNode)
                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
                            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;
    }
    

    hash算法

    // JDK7
    final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
    
        h ^= k.hashCode();
    
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    
    // JDK8
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

    实体结构

    // JDK7
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
    
        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
    }
    
    // JDK8
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
    
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }
    
    static class LinkedHashMap.Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K, V> parent;  // red-black tree links
        TreeNode<K, V> left;
        TreeNode<K, V> right;
        TreeNode<K, V> prev;    // needed to unlink next upon deletion
        boolean red;
    
        TreeNode(int hash, K key, V val, Node<K, V> next) {
            super(hash, key, val, next);
        }
    }
    

    get过程

    JDK7
    // JDK7
    public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
    
        return null == entry ? null : entry.getValue();
    }
    
    final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }
    
        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
    
    // JDK8
    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    
    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;
    }
    

    JDK7&JDK8区别

    • 底层结构
      JDK7是数组+链表 存储节点数据Entry
      JDK8是数组+链表+红黑树 存储节点数据Node。JDK8当链表长度大于8同时HashMap元素个数大于64则转换成红黑树
    • 哈希表为空
      JDK7会先调用inflateTable初始化一个数组
      JDK8则直接调用热size扩容
    • hash冲突时插入数据
      JDK7采用头插
      JDK8会将节点插入到链表尾部
    • hash函数
      JDK7中的hash函数直接使用key的hashcode值进行5次异或和4次位移,扰动复杂
      JDK8中的hash函数采用key的hashcode值异或上key的hashcode无符号右移16位,扰动简单
    • 扩容策略
      JDK7中会重新计算hash
      JDK8中不需要像JDK7那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”
      JDK8扩容

    相关文章

      网友评论

        本文标题:HashMap

        本文链接:https://www.haomeiwen.com/subject/kvrgsrtx.html