HashMap-----数据结构、常量、成员变量、构造方法

  HashMap是基于哈希表的Map接口的实现,以键值对(key, value)的形式存在。在HashMap中,key-value总是会当作一个整体来处理,系统会根据hash算法来计算key-value的存储位置,我们可以通过key快速的存、取value。下面我们将会对HashMap的源码进行详细的解读,主要围绕:

  • HashMap基本源码剖析(构造方法、存、取元素等)
  • HashMap内部实现基本点分析
  • 容量、负载因子、树化

一、定义

  HaspMap实现了Map接口,继承AbstractMap类。其中Map接口定义了键映射到值的规则AbstractMap类提供Map接口的实现方法

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

二、数据结构

  HashMap内部结构是数组(Node[] table)和链表结合组成的复合结构,数组被分成一个个桶(bucket),通过哈希值决定键值对在这个数组的寻址;哈希值相同的键值对,则以链表形式存储。需注意的是:链表大小超过阈值(TREEIFY_THRESHOLD = 8)时,链表就会被改造成树形结构。 下面示意图为一个简单的HashMap示意图:

在这里插入图片描述

三、常量

1. 默认容量

	/**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

默认初始化的容量时16,必须是2的幂次方。

2. 最大容量

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

最大的容量是2^30。

3. 默认负载因子

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

默认的负载因子是0.75

4. 树化阈值:由链表转换成红黑树

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

一个桶中bin的存储方式由链表转换成树的阈值。即当桶中bin的数量超过TREEIFY_THRESHOLD时使用树来代替链表。默认值是8

5. 解树化阈值:由红黑树转换成链表

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

当执行resize扩容操作时,当桶中bin的数量少于UNTREEIFY_THRESHOLD时使用链表来代替树。默认值是6 。

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;

当桶中的bin被树化时最小的hash表容量。(如果没有达到这个阈值,即hash表容量小于MIN_TREEIFY_CAPACITY,当桶中bin的数量太多时会执行resize扩容操作)这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。

7. 总结

DEFAULT_INITIAL_CAPACITY = 1 << 4(桶个数16)

DEFAULT_LOAD_FACTOR = 0.75f(负载因子0.75)

TREEIFY_THRESHOLD = 8(树化阈值8)

MIN_TREEIFY_CAPACITY = 64(树化要求的最少哈希表元素数量)

UNTREEIFY_THRESHOLD = 6(解除树化的阈值,在resize阶段)

四、成员变量

  在我们学习HashMap时,总会听到负载因子、阈值、容量等变量的名字,那么我们在先对HashMap中的成员变量做一个介绍。

1. table

    /**
     * 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的幂次方。
     * 我们也在一些操作中允许数组长度为0,目前不需要引导机制
     */
    transient Node<K,V>[] table;

总结:

  • table是一个用于存放键值对的数组
  • 第一次使用(插入元素)时被初始化,根据需要可以重新分配空间(扩容)。
  • 分配的空间长度必须是2的幂次方。

2. entrySet

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     * 保存缓存的entrySet()。注意,它的父接口中用于keySet()和values()方法中有使用
     */
    transient Set<Map.Entry<K,V>> entrySet;

  当被调用entrySet时被赋值。通过keySet()方法可以得到Map中的key集合,通过values可以得到Map中的value集合。

3. size

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;

  该值用于存放Map中键值对的个数。

4. modCount

/**
     * 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).
     */
    transient int modCount;

  HashMap被结构性修改的次数。(结构性修改是指改变了KV映射数量的操作或者修改了HashMap的内部结构。modCount的使用我们在后面的putVal()中有使用到。

5. threshold

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

  阈值,当HashMap中的键值对数量超过了阈值,就会扩容。
thresold = capacity * loadFactor

6. loadFactor

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;

  负载因子

HashMap的成员变量中没有capacity变量不过我们可以通过threshold和loadFactor计算得到capacity。

五、构造函数

HashMap提供了三个构造函数:

  • HashMap():构造一个具有默认初始容量(16)和默认的负载因子(0.75)的空HashMap。
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    // 负载因子为0.75
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • HashMap(int initialCapacity):构造一个具有指定的初始容量和默认负载因子(0.75)的空HashMap。
    public HashMap(int initialCapacity) {
    	// 在这里调用了两个参数的构造方法,传入指定的初始化容量和默认的负载因子
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
  • HashMap(int initialCapacity, float loadFactor):构造一个具有指定的初始化容量和负载因子的空HashMap.
    public HashMap(int initialCapacity, float loadFactor) {
    	// 1. 指定的初始化容量小于0,抛出异常IllegalArgumentException
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: "+
                                               initialCapacity);
        // 2. 调整最大容量
        // 指定的初始化容量大于集合的最大容量MAXIMUM_CAPACITY
        // static final int MAXIMUM_CAPACITY = 1 << 30;
        if (initialCapacity > MAXIMUM_CAPACITY)
        	// 将指定的初始化容量改为集合的最大容量MAXIMUM_CAPACITY
            initialCapacity = MAXIMUM_CAPACITY;
        // 3. 负载因子为非正值,抛出异常IllegalArgumentException
        // Float.isNaN(loadFactor):该方法用于描述非法的float数
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        // 4. 指定的负载因子为正值,将其赋给HashMap的负载因子
        this.loadFactor = loadFactor;
        // 5. 将传入的初始化容量initialCapacity做计算,返回一个大于等于initialCapacity最小的2的幂次方。
        this.threshold = tableSizeFor(initialCapacity);
    }

tableSizeFor(initialCapacity): 将传入的initialCapacity做计算,返回一个大于等于initialCapacity的最小2的幂次方。(就是不管你传入的初始化Hash桶的长度参数为多少,最后通过这个方法会将Hash表的初始化长度改为2的幂次方 )
例如:你确定的initialCapacity=6,计算出来的结果就为8。

    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

tableSizeFor()方法详解请看博文:https://blog.csdn.net/meng_lemon/article/details/88874499
   在构造函数这里提到了两个参数:初始容量负载因子。这两个参数是影响HashMap性能的重要参数。

  • 容量: 哈希桶中的数量
  • 初始容量: 创建哈希表时的容量
  • 负载因子: 哈希表在容量增加可以达到的最大的尺度,它是衡量一个散列表的空间使用成都,负载因子越大表示散列表的装填成都越高。

  通过构造函数来看,桶数组在最开始的时候并没有初始化好,仅仅设置了一些初始值,这是我们可能会怀疑HashMap是按照lazy-load(懒加载)原则,在首次使用时初始化。我们这时候会想到ArrayList,它是在第一次添加元素使用(添加元素)时才初始化数组,开辟数组空间。
put()方法详解请看博文:
https://blog.csdn.net/meng_lemon/article/details/88906980
  在put()方法中,需要初始化数组,开辟数组空间,在数组中元素超过阈值时,常常需要扩容。那么这些都是怎么实现的?我们可以知道一个resize()方法。
resize()方法详解请看博文:https://blog.csdn.net/meng_lemon/article/details/88907241

猜你喜欢

转载自blog.csdn.net/meng_lemon/article/details/88857670
今日推荐