HashMap源码详解:Core Java 9.3

如果确切知道HashMap将要处理的数据量为capacity,则推荐调用构造函数public HashMap(int initialCapacity)来创建 ,此构造函数会设定阈值threshold为大于用户给定的capacity的2的乘方的最小值(例如,假如参数initialCapacity的值是13-16中的任意一个值,threshold都会是16)。
用户在主动设定capacity后不必担心自动扩容问题,因为扩容只会在实际运行时HashMap中的数据量>=threshold时发生,而调用构造函数public HashMap(int initialCapacity)时以下关系必定成立:threshold>=用户设定的capacity,因此只要实际的数据量确实不大于capacity,则自动扩容一定不会发生。

Map接口

keySet values size containsKey put remove
entrySet isEmpty containsValue get clear

以下为部分JDK1.8添加的默认方法,default

getOrdefault(Object o,V v) replaceAll(BiFunction<K,V,V> f) remvoe(K k,V v)
forEach(BiConsumer<K,V> c) putIfAbsent(K k,V v) replace

Map.Entry接口

此接口是定义在Map接口内部的static的接口

getKey setValue comparingByKey comparingByKey(Comparator c)
getValue equals comparingByValue comparingByValue(Comparator c)

HashMap

源码解析:

public class HashMap<K,V> extends AbstractMap<K,V>  implements Map<K,V>, Cloneable, Serializable {
定义一些默认值
	// table的初始大小默认值,即桶个数,必须是2的乘方
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    
	// table的大小的最大值,即桶数的最大值
    static final int MAXIMUM_CAPACITY = 1 << 30;
    
	// 负载因子,建议0.5-1.5
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    // 当某个桶内元素个数大于等于此数,并且桶数大于64时,会将桶内元素的存储结构由单链表改为红黑树
    static final int TREEIFY_THRESHOLD = 8;
    
    // 当桶内元素个数小于或者等于此数时,会将桶内元素的存储结构由红黑树改为单链表
    static final int UNTREEIFY_THRESHOLD = 6;
    
     // 若table的大小小于MIN_TREEIFY_CAPACITY 时,即便某个桶内的元素个数达到了TREEIFY_THRESHOLD 后,也并不会对这个桶做树化操作,而是对map进行扩容resize()
    static final int MIN_TREEIFY_CAPACITY = 64;
定义内部类:封装链表的节点
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;
        }
    }
定义实例域

注意

  • 并没有定义一个capacity实例域来指明table数组的大小,尽管类中定义了一个静态常量DEFAULT_INITIAL_CAPACITY。
  • table数组的大小是在初始化时确定的:参看resize()方法。
  • 所有实例域的访问控制都是默认的
 /* ---------------- Fields -------------- */

	// 装桶的数组,存储每个桶内的单链表的头结点或者树的根节点
    transient Node<K,V>[] table;
    
    transient Set<Map.Entry<K,V>> entrySet;

    // Map中当前实际存储的元素个数
    transient int size;

	// 每次remove,add等都会++modCount,当并发时,发现自己的modCount不是原来的了,就会抛出异常,表示并行修改失败
    transient int modCount;

	// 阈值:当map中的元素个数大于等于threshold时,会触发resize()操作进行扩容。
    int threshold;

	// 负载因子:当用户调用的默认无参构造函数、或者map自动扩容时,新的threshold=新table的capacity*loadFactor;
    final float loadFactor;
定义构造函数

注意:

  • 如果用户确切知道将要处理的数据量为capacity,则可以调用构造函数public HashMap(int initialCapacity) ,此构造函数会设定阈值threshold为大于用户给定的capacity的2的乘方的最小值。
    因此用户在主动设定capacity后不必担心自动扩容问题,因为扩容只会在实际数据量>=threshold时发生,而此种情况下threshold>=用户设定的capacity会一定成立。
    参见:tableSizeFor()方法
  /* --------------------------构造函数,不会初始化table数组,table数组只有在首次调用put方法时才会被初始化------ */
   
    // 默认构造函数,只设置了负载因子的默认值
   public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
	public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }    

    // 初始化loadFactor 、threshold 的值
    // threshold = 大于initialCapacity的最小的2乘方(如,15 ->16 ,13->16)
 public HashMap(int initialCapacity, float loadFactor) {
       ...
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
    // 计算出大于cap的最小的2的乘方
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
       
        n |= n >>> 1;  // 00001 0010 | 0000 1001 -> 11011
        n |= n >>> 2;  //  0001 1011 | 0000 0110 -> 11111
        n |= n >>> 4;  //  0001 1111 | 0000 0001 -> 11111
        n |= n >>> 8;  //  
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
    
put方法
  • 如果table还未被初始化,则调用resize()进行初始化
  • 如果散列到桶中之后,桶内元素个数>=TREEIFY_THRESHOLD ,则调用treeifyBin()方法检查是否要将桶改为红黑树结构
  • 如果散列到Map中之后,Map中元素个数>=threshold了,则调用resize()方法进行扩容
    /* ------------------------------put方法------------------------------------------ */
    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;  // n:table的大小。i:新节点的桶号
        // 如果还未被初始化过,则调用resize(); HashMap在首次调用put方法之前,是不会初始化table的,因为那样的话会浪费一块连续内存。
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 根据key的哈希值,确定桶号,如果桶中还没有元素,则直接将其作为头结点存储到桶中
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        // 获取到了头结点
        else {
            Node<K,V> e; // 目标节点)
            K k;
            // 如果头结点的key和新节点的key相同,则头结点即为要被取代的目标节点
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // 如果头结点是TreeNode类型的,则调用putTreeVal方法将新节点插入,并返回插入后的目标节点
            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) {
                    	// 如果没有找到key相同的节点,就直接追加到链表尾部
                        p.next = newNode(hash, key, value, null);
                        // 如果桶中元素数是达到了设定的变树阈值(默认值8),则需要将桶内元素的存储结构更新为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 如果链表中有和新节点的key相同的元素
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // map中有与新节点的key相同的元素,那么根据条件做一些操作,就返回
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                // onlyIfAbsent 是方法参数,表示只有不存在相同key的节点时,才进行更新操作,如果有相同节点,则不做任何操作。
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);  //此处是留给LinkedHashMap用的。
                return oldValue;
            }
        } // else结束
        // 更新了hashMap,就执行++modCount;
        ++modCount;
        // 当map中的元素数量大于阈值,就要扩容再散列
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
对table数组初始化 \ 扩容

resize()方法内有两种逻辑:

  • 一种是当前table为null时,会对table进行初始化操作;
  • 一种是当前table非null,会对table进行扩容操作;
    final Node<K,V>[] resize() {
    	// 当前table
        Node<K,V>[] oldTab = table;
        // 当前table的大小
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // 当前的阈值
        int oldThr = threshold;
        int newCap, newThr = 0;
        // oldCap>0,说明是要做扩容操作
        if (oldCap > 0) {
	        // 如果原本的table的大小已经是最大值,无法继续扩容,直接退出
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 扩容:设置新的table的大小为原来的2倍,新的threshold也为原来的2背
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        // oldCap<= 0,说明table还未被初始化过,要进行初始化table的操作;oldThr>0,说明用户调用的有参构造函数,设置了threshold;直接将根据用户参数计算出的阈值设定为table的大小
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        // oldCap<= 0,说明是要进行初始化table的操作;oldThr<0,说明用户调用的默认无参构造函数;则将各个域变量的值设置为默认值。
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 如果经过以上设置,newThr 仍为0,(什么情况下会出现?)
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        
		// 无论是要初始化table,还是要对table进行扩容,经过以上逻辑,都已经确定了要新创建的table的大小、threshold 。
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        // 如果当前table不为null,说明需要进行扩容操作
        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;
    }
©️2020 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值