JDK17中HashMap的源码:【好恶心,居然不能用插入代码的方式写代码】

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    // 初始容量: 2的4次方 -> 16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    // 最大容量: 2的30次方 -> 1073741824
    static final int MAXIMUM_CAPACITY = 1 << 30;
    // 如果没有指定扩容因子,默认 0.75
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 使用树而不是列表的bin计数阈值。当向至少有这么多节点的桶中添加元素时,桶将转换为树。该值必须大于2,并且应该至少为8。
    static final int TREEIFY_THRESHOLD = 8;
    // 在调整大小操作期间取消树化(拆分)bin的bin计数阈值。应小于TREEIFY_THRESHOLD,且最多为6,以便在移除时进行收缩检测。
    static final int UNTREEIFY_THRESHOLD = 6;
    // 表,在第一次使用时初始化,并根据需要调整大小。在分配时,长度总是2的幂。
    transient Node<K,V>[] table;
    // 保存缓存的entrySet()。注意,AbstractMap字段用于keySet()和values()。
    transient Set<Map.Entry<K,V>> entrySet;
    // 基本哈希bin节点,用于大多数条目。
    static class Node<K,V> implements Map.Entry<K,V> {

        final int hash;

        final K key;

        V value;

    Node<K,V> next;

        // 省略...

    }

    // 从内部类TreeNode可以得知,典型的树结构

    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;

        // 省略...
    }


}

可以从以下几个方面去读:

1. HashMap的存储结构是怎样的?

2. HashMap的put()方法是如何工作的?

3. HashMap是如何扩容的?

 

Q1:HashMap的存储结构是怎样的?

由源码可知:

在HashMap中,数据存储一共分为两种结构:

  • 链表散列:数组+链表
  • 红黑树结构

哈希表结构(链表散列:数组+链表)实现,结合数组和链表的优点。当链表长度超过 8 时,链表转换为红黑树。transient Node<K,V>[] table;

 

Q2:HashMap的put()方法是如何工作的?

HashMap 底层是 hash 数组和单向链表实现,数组中的每个元素都是链表,由 Node 内部类(实现 Map.Entry<K,V>接口)实现,HashMap 通过 put & get 方法存储和获取。

存储对象时,将 K/V 键值传给 put() 方法:

1. 调用 hash(K) 方法计算 K 的 hash 值,然后结合数组长度,计算得数组下标;

2. 调整数组大小(当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n);

3. i.如果 K 的 hash 值在 HashMap 中不存在,则执行插入,若存在,则发生碰撞;

ii.如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 true,则更新键值对;

iii. 如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 false,则插入链表的尾部(尾插法)或者红黑树中(树的添加方式)。

【注意:当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树】

 

Q3:HashMap是如何扩容的?

创建一个新的数组,其容量为旧数组的两倍,并重新计算旧数组中结点的存储位置。结点在新数组中的位置只有两种,原下标位置或原下标+旧数组的大小。

posted on 2023-08-11 00:45  鬼谷玄一  阅读(9)  评论(0编辑  收藏  举报