一. HahMap

1.1 底层数据结构

1.1.1 java7和java8的不同之处

HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一,是非线程安全的。
HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个
JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。
JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, HashMap 总是使用 2 的幂作为哈希表的大小。

1.1.2 扰动函数/hash算法

JDK 1.8 的 hash 方法

  1. static final int hash(Object key) {
  2. int h;
  3. // key.hashCode():返回散列值也就是hashcode
  4. // ^ :按位异或
  5. // >>>:无符号右移,忽略符号位,空位都以0补齐
  6. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  7. }

1.1.3 拉链法(1.8以前)

java7中,讲数组和链表进行结合。

  1. 要存储一个元素,首先用key,计算出hash值,这个hash值来决定其在数组中的位置,然后放入数组对应的索引位置,这个索引位置上面村的是一个链表节点。
  2. 接着存储第二个元素,如果hash值计算出来同第一个元素时一样的(即发生了hash碰撞),然后判断key是否相同,如果相同,那么直接覆盖掉第一次插进来的元素。如果不同的话,那么就加到第一个元素所在链表中。

image.png

1.1.4 数组+链表+红黑树(1.8以后)

当链表长度大于阈值(默认为 8)时,会首先调用 treeifyBin()方法。这个方法会根据 HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是执行 resize() 方法对数组扩容。相关源码这里就不贴了,重点关注 treeifyBin()方法即可!

  1. /**
  2. * The smallest table capacity for which bins may be treeified.
  3. * (Otherwise the table is resized if too many nodes in a bin.)
  4. * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
  5. * between resizing and treeification thresholds.
  6. */
  7. static final int MIN_TREEIFY_CAPACITY = 64;
  8. /**
  9. * Replaces all linked nodes in bin at index for given hash unless
  10. * table is too small, in which case resizes instead.
  11. */
  12. final void treeifyBin(Node<K,V>[] tab, int hash) {
  13. int n, index; Node<K,V> e;
  14. if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
  15. resize();
  16. else if ((e = tab[index = (n - 1) & hash]) != null) {
  17. TreeNode<K,V> hd = null, tl = null;
  18. do {
  19. TreeNode<K,V> p = replacementTreeNode(e, null);
  20. if (tl == null)
  21. hd = p;
  22. else {
  23. p.prev = tl;
  24. tl.next = p;
  25. }
  26. tl = p;
  27. } while ((e = e.next) != null);
  28. if ((tab[index] = hd) != null)
  29. hd.treeify(tab);
  30. }
  31. }

HashMap详解 - 图2

1.2 各种属性

1.2.1 各种属性详解

    private static final long serialVersionUID = 362498820763181265L;
    /**
     * The default initial capacity - MUST be a power of two.
     * 默认的初始容量16,必须时2的幂次方
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     * 最大容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     * 默认的加载因子,用来判断数组疏密程度,用以判断是否进行扩容
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     * 树化的临界值,由链表转化为红黑树的临界值
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     * 非树化临界值,即由红黑树在转换成链表的临界值
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     * 进行树话的时候,最小要求数组中的个数
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     * 存储元素的数组,总是2的幂次倍
     */
    transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     * 存放具体元素的集
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * The number of key-value mappings contained in this map.
     * 存放着的元素的个数,注意不要和数组的长度搞混了
     */
    transient int size;

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     * 每次扩容和更改map结构的计数器
     */
    transient int modCount;

    /**
     * The next size value at which to resize (capacity * load factor).
     *
     * @serial
     */
    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    // 用于判断,是否对数组进行扩容
    int threshold;

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;
  • loadFactor 加载因子loadFactor 加载因子是控制数组存放数据的疏密程度,loadFactor 越趋近于 1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor 越小,也就是趋近于 0,数组中存放的数据(entry)也就越少,也就越稀疏。loadFactor 太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor 的默认值为 0.75f 是官方给出的一个比较好的临界值。给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。
  • thresholdthreshold = capacity * loadFactor当 Size>=threshold的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是 衡量数组是否需要扩增的一个标准

1.3 节点相关

1.3.1 Node节点(即链表节点)

Node节点时放在数组中的元素,其结构如下图所示

    /**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     */
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;  // hash值,存放元素到hashMap中时,用来与其他元素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;
        }

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

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

1.3.2 TreeNode节点(即树节点)

红黑树部分的代码,还有很多代码没贴,太多太难了。

    /**
     * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
     * extends Node) so can be used as extension of either regular or
     * linked node.
     */
    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);
        }

        /**
         * Returns root of tree containing this node.
         */
        final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }

1.4 构造方法

HashMap 中有四个构造方法,它们分别如下:

    /**
     * Constructs an empty {@code HashMap} with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     * 指定“容量大小”和“加载因子”的构造函数
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    /**
     * Constructs an empty {@code HashMap} with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     * 指定“容量大小”的构造函数
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty {@code HashMap} with the default initial capacity
     * (16) and the default load factor (0.75).
     * 默认构造函数。
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * Constructs a new {@code HashMap} with the same mappings as the
     * specified {@code Map}.  The {@code HashMap} is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified {@code Map}.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     * 包含另一个“Map”的构造函数
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

putMapEntries方法

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        // 判断table是否已经初始化
        if (table == null) { // pre-size
            // 未初始化,s为m的实际元素个数
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                    (int)ft : MAXIMUM_CAPACITY);
            // 计算得到的t大于阈值,则初始化阈值
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        // 已初始化,并且m元素个数大于阈值,进行扩容处理
        else if (s > threshold)
            resize();
        // 将m中的所有元素添加至HashMap中
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}

1.5 put方法

1.5.1 putVal方法

HashMap 只提供了 put 用于添加元素,putVal 方法只是给 put 方法调用的一个方法,并没有提供给用户使用。
对 putVal 方法添加元素的分析如下:

  1. 如果定位到的数组位置没有元素 就直接插入。
  2. 如果定位到的数组位置有元素就和要插入的 key 比较,如果 key 相同就直接覆盖,如果 key 不相同,就判断 p 是否是一个树节点,如果是就调用e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value)将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)。

HashMap详解 - 图3
说明:上图有两个小问题:

  • 直接覆盖之后应该就会 return,不会有后续操作。
  • 当链表长度大于阈值(默认为 8)并且 HashMap 数组长度超过 64 的时候才会执行链表转红黑树的操作,否则就只是对数组扩容。 ```java /**

    • Associates the specified value with the specified key in this map.
    • If the map previously contained a mapping for the key, the old
    • value is replaced. *
    • @param key key with which the specified value is to be associated
    • @param value value to be associated with the specified key
    • @return the previous value associated with key, or
    • null if there was no mapping for key.
    • (A null return can also indicate that the map
    • previously associated null with key.) */ public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }

      /**

    • Implements Map.put and related methods. *
    • @param hash hash for key
    • @param key the key
    • @param value the value to put
    • @param onlyIfAbsent if true, don’t change existing value
    • @param evict if false, the table is in creation mode.
    • @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

              boolean evict) {
      

      Node[] tab; Node p; int n, i; // table未初始化或者长度为0,进行扩容 if ((tab = table) == null || (n = tab.length) == 0)

       n = (tab = resize()).length;
      

      // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中) if ((p = tab[i = (n - 1) & hash]) == null)

       tab[i] = newNode(hash, key, value, null);
      

      else {

       // 桶中已经存在元素
       Node<K,V> e; K k;
      
       // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
       if (p.hash == hash &&
           ((k = p.key) == key || (key != null && key.equals(k))))
           // 将第一个元素赋值给e,用e来记录
           e = p;
      
       // hash值不相等,即key不相等;为红黑树结点
       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);
                   // 结点数量达到阈值(默认为 8 ),执行 treeifyBin 方法
                   // 这个方法会根据 HashMap 数组来决定是否转换为红黑树。
                   // 只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。
                   //否则,就是只是对数组扩容。
                   if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                       treeifyBin(tab, hash);
                   break;
               }
               // 判断链表中结点的key值与插入的元素的key值是否相等
               if (e.hash == hash &&
                   ((k = e.key) == key || (key != null && key.equals(k))))
                   break;
               p = e;
           }
       }
       // 表示在桶中找到key值、hash值与插入元素相等的结点
       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; }

<a name="P087w"></a>
### 1.5.2 扩容方法
resize()进行扩容,会伴随着一次重新 hash 分配,并且会遍历 hash 表中所有的元素,是非常耗时的。在编写程序中,要尽量避免 resize。<br />则容量变为原来的2倍,阈值也变为原来的2倍

**什么时候触发扩容?**<br />一般情况下,**当元素数量超过阈值时**便会触发扩容。每次扩容的容量都是之前容量的2倍。<br />HashMap的容量是有上限的,必须小于**1<<30**,即1073741824。如果容量超出了这个数,则不再增长,且阈值会被设置为**Integer.MAX_VALUE**( ![](https://cdn.nlark.com/yuque/0/2021/svg/1439922/1637912688705-edf43f72-7a37-471c-92dd-60452a0c231f.svg#clientId=uca714f7e-cfa9-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u77186a2e&margin=%5Bobject%20Object%5D&originHeight=20&originWidth=49&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u3f7a2ee5-6bf9-404f-8c26-7ab6d533781&title=) ,即永远不会超出阈值了)。
```java
    /**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

1.5.3 树化代码

    /**
     * Replaces all linked nodes in bin at index for given hash unless
     * table is too small, in which case resizes instead.
     */
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

二. 桶

2.1 桶时什么?

HashMap的数组中,每个元素称之为“”。需要注意的是,这个“桶”并不等同于“键值对(Entity)”。至于它是什么请往下看。
初始化
HashMap在初始化时会创建一个Entity的数组。其个数为16。其源码类似下面的代码:
其中key和value不必多说,不过它还包含了一个next属性,这说明它可以组织成一个链表结构。
因此,所谓的“桶”就是数组每个位置放置的“链表”

/**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     */
    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;
        }

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

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