HashMap源码详解

  • DEFAULT_INITIAL_CAPACITY
    如果我们创建HashMap对象时不初始化容量,此时就会使用默认的初始值为16
/**
 * The default initial capacity - MUST be a power of two.
 */

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  • MAXIMUM_CAPACITY
    HashMap的最大容量为2的32次幂;当我们在创建HashMap时,如果我们的容量大于这个最大容量,那么会自动修改为这个最大容量(在构造方法会看到)
 /**
  * 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;
  • DEFAULT_LOAD_FACTOR
    HashMap的初始化装载因子为0.75(如果初始化HashMap时没有设置load factor,那么就会使用这个默认值)
/**
 * The load factor used when none specified in constructor.
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • TREEIFY_THRESHOLD
    当一个桶中的元素超过了8,那么此时就会由链表转换成红黑树
/**
     * 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;
  • UNTREEIFY_THRESHOLD
    当一个桶中的元素少于6时会从红黑树转换成链表
/**
     * 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;
  • MIN_TREEIFY_CAPACITY
    如果当一个table中最小容量超过64时,会在bin中采用红黑树的结构。
/**
 * 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;

看完上面是不是很模糊,到底什么时候一个桶会有链表转换成红黑树。这是有两个先决条件决定的。

  • 一个table的容量小于MIN_TREEIFY_CAPACITY,那么此时一个bin中的元素如果超过了TREEIFY_THRESHOLD,此时不会将数据结构由链表转化为红黑树,只是对table进行一个简单的扩容处理,同时将链表元素冲突超过TREEIFY_THRESHOLD的链表分成两个链表。
  • 如果一个table的容量大于MIN_TREEIFY_CAPACITY,那么此时一个bin中的元素如果超过了TREEIFY_THRESHOLD,那么此时数据结构会由链表转化为红黑树。同时如果一个bin中的元素少于UNTREEIFY_THRESHOLD那么此时会由红黑树转化为链表的数据结构

———————————————————————————————————————

由transient方法修饰的变量

  • 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.)
     */
    //table.length 通常设置为2的整数次幂
    transient Node<K,V>[] table;

我们可以看到table是一个有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> {
     //一个Node包含一个hash、key-value(键值对)、next指向下一个节点的指针
     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; }

     //覆写Object对象的hashCode方法,用于比较两个Node是否相同的必要条件
     public final int hashCode() {
         return Objects.hashCode(key) ^ Objects.hashCode(value);
     }

     public final V setValue(V newValue) {
         V oldValue = value;
         value = newValue;
         return oldValue;
     }

     //重写equals方法就必需重写hashCode方法
     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;
     }
}
  • entrySet
  • size
    map中包含的实际的键值对(key-value)数量
/**
 * The number of key-value mappings contained in this map.
 */
 transient int size;
  • modCount
    对HashMap操作的次数
transient int modCount;
  • threshold
    Hashtable resize 的门限值,一旦HashMap的size超过了这个threshold就需要对HashMap进行扩容
/**
 * The next size value at which to resize (capacity * load factor).
 *
 * @serial
 */
 int threshold;
  • loadFactor
    Hashtable 的装载因子,
/**
 * The load factor for the hash table.
 *
 * @serial
 */
 final float loadFactor;

构造方法

  • HashMap(int initialCapacity, float loadFactor)
    通过一个具体的初始容量initialCapacity和loadFactor来构造一个空的HashMap(暂时没有存key-value)
public HashMap(int initialCapacity, float loadFactor) {
    //初始容量必须大于或等于0
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);

    //初始容量如果大于最大的默认的容量,那么修正初始容量为默认最大容量
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;

    //装载因子必须大于0,且是一个相对常量(不能是无穷大)
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
    }
  • HashMap(int initialCapacity)
    改构造方法会调用上面的构造方法,同时因为我们没有设置装载因子,那么我们就会使用默认的装载因子(0.75),构造一个空的HashMap。
public HashMap(int initialCapacity) {
    //调用HashMap(initialCapacity,DEFAULT_LOAD_FACTOR)方法
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
  • HashMap()
    构造一个装载因自为默认装载因子(0.75),table的初始容量设置为默认的初始容量(16)的empty HashMap
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

添加键值对

  • put(K key,V value)
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;
        //步骤一:判断table是否为空或者长度为零,是则扩容,不是则转向步骤二
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

        //步骤二:如果key映射到table中对应位置的链表为空,那么我们新添加的键值对就作为头结点
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //步骤三:如果key映射到table中位置的链表(or 红黑树)的头结点的key相等,
            //那么直接覆盖头结点的value值即可,如果不成立转到步骤四
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //步骤四:如果映射到table的中位值的头结点是TreeNode,那么调用putTreeVal方法插入键值对,如果此条件不成立则转向步骤五
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //步骤五:遍历链表,如果链表中Node.key存在与key相等的Node,那么此时就覆盖value;
                //如果不存在,那么首先创建一个Node,然后将改Node添加在链表的末尾
                //添加完节点后,需要判断滨bin中的Node数量是否超过了对应的,如果超过了则将链表的结构转换成红黑树,否则不执行
                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;
        //步骤六:如果table中key-value的数量超过了规定的数量,那么此时就应该进行扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    执行步骤
步骤1、判断table是否为空或者长度为零,是则扩容;不是则跳过该步骤,转向步骤二

步骤2、如果key映射到table中对应位置的链表为空,那么我们新添加的键值对就作为头结点;如果该条件不成立则转向步骤三

步骤3、如果key映射到table中位置的链表(or 红黑树)的头结点的key相等,那么直接覆盖头结点的value值即可,如果不成立转到步骤四

步骤4、如果映射到table的中位值的头结点是TreeNode,那么调用putTreeVal方法插入键值对,如果此条件不成立则转向步骤五

步骤5、遍历链表,如果链表中Node.key存在与key相等的Node,那么此时就覆盖value;如果不存在,那么首先创建一个Node,然后将改Node添加在链表的末尾添加完节点后,需要判断滨bin中的Node数量是否超过了对应的,如果超过了则将链表的结构转换成红黑树,否则不执行

  • 对于一个给定的key我们如果在table中找到其对应的位置?

1、计算给定key的hashCode,将key的hash值与其高16位进行或运算得到hash(key)

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

2、利用(table.length-1)&hash(key)就得到了key在table数组中的索引

(n - 1) & hash
  • resize() 的实现
    final Node<K,V>[] resize() {
        //定义一个oldTable引用table,定义oldCap表示oldTable数组的大小,
        //定义oldThr表示oldTable扩容的门限值
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;

        if (oldCap > 0) {
            //如果之前的oldTable的容量已经不可扩充了,那么直接返回
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //newTable的容量首先扩容为之前的两倍,如果扩容之后比默认初始容量要大
            //但是小于最大可扩容的容量,此时扩容的门限值也变成之前的两倍
            //根据threshold = capacity*loadfactor,我们的loadfactor是不变的,因此newThr相应翻倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        //如果table的容量已经初始化,那么
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        //如果table未经初始化,那么我们table的capacity和threshold就先初始化
        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引用newTable的地址
        table = newTab;
        if (oldTab != null) {
            //遍历table数组
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //如果oldTable的索引j处只有一个节点
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    //如果oldTable的索引j处存储的是一个red-black tree,那么就调用相应的split()方法解决
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);

                    //如果oldTable的第j条链表有多个节点的情况
                    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;
                            //如果e.hash值小于oldCap,那么此时(e.hash & oldCap) == 0
                            //也就是说e.hash&(newCap - 1) = e.hash
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //如果e.hash值小于oldCap,那么此时(e.hash & oldCap) == 1
                            //也就是说e.hash&(newCap - 1) = e.hash + oldCap
                            //例子 oldCap = 16;hash(key1) = 8;newCap = 32
                            0000 0000 0000 0000 0000 0000 0000 0000
                            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, oldCap = 16;hash(key1) = 8;newCap = 32

变量名 对应二进制 &后的结构
hash(key1) 0000 0000 0000 0000 0000 0000 0000 1000
oldCap-1 0000 0000 0000 0000 0000 0000 0000 1111 0000 0000 0000 0000 0000 0000 0000 1000
oldCap 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000
newCap-1 0000 0000 0000 0000 0000 0000 0001 1111 0000 0000 0000 0000 0000 0000 0000 1000

从这个例子我们可以看到,如果我们的oldCap=16,那么我们key1 用

hash(key1)&oldCap + hash(key1) =hash(key1)&(newCap)

也就是说如果我们的在扩容之前,如果key的hash值小于扩容之前的容量(oldCap),那么不管在扩容之前还是扩容之后key就会映射到与其hash值相同的位置
例1, oldCap = 16;hash(key1) = 17;newCap = 32

变量名 对应二进制 &后的结构
hash(key1) 0000 0000 0000 0000 0000 0000 0001 0001
oldCap-1 0000 0000 0000 0000 0000 0000 0000 1111 0000 0000 0000 0000 0000 0000 0000 0001
oldCap 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0001 0000
newCap-1 0000 0000 0000 0000 0000 0000 0001 1111 0000 0000 0000 0000 0000 0000 0001 0001

从这个例子我们可以看到,如果我们的oldCap=16,那么我们key1 用hash(key1)&oldCap + hash(key1) = ;也就是说如果我们的在扩容之前,如果key的hash值小于扩容之前的容量(oldCap),那么不管在扩容之前还是扩容之后key就会映射到与其hash值相同的位置

猜你喜欢

转载自blog.csdn.net/makeliwei1/article/details/80217943