加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

JDK1.8_HashMap源码__构造函数

发布时间:2020-12-15 01:57:44 所属栏目:Java 来源:网络整理
导读:HashMap的底层实现是一个Node类型的数组,也就是说使用put(key,value)方法的时候就把key和value根据hashcode值存在table数组相应的下标中,源码如下: /** * The table,initialized on first use,and resized as * necessary. When allocated,length is alwa

HashMap的底层实现是一个Node类型的数组,也就是说使用put(key,value)方法的时候就把key和value根据hashcode值存在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.)
     */
    transient Node<K,V>[] table;

Node是一个实现了Map.Entry的泛型类,源码如下:

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

2 HashMap的构造函数

HashMap共有四个构造函数:

public HashMap()
public HashMap(int initialCapacity)
public HashMap(int initialCapacity,float loadFactor)
public HashMap(Map<? extends K,? extends V> m)

2.1 无参构造函数:public HashMap()

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

官方在HashMap()构造方法的注释里说到:“使用默认初始化容量(16)和默认加载因子(0.75)来构造一个空的HashMap”,但是源码中只初始化了loadFactor而没有初始化默认容量,那么容量的初始化在哪里呢?

既然容量在构造函数里没有进行初始化,那么在使用put方法往hashMap中添加元素会发生怎么情况呢?

下面就让我们来put方法中一探究竟吧!

public V put(K key,V value) {
      //这里调用了下面的putVal方法
return putVal(hash(key),key,value,false,true); }

final V putVal(int hash,boolean onlyIfAbsent,boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n,i; //如果table为空或者长度为0,先进行扩容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;       //省略剩余代码。。。。。。 }

为了不干扰这里先把暂时用不到的代码忽略掉,从源码中的黄色高亮代码中我们可以看到,在put的时候当table为null或者长度为0时将会调用它resize方法进行一次扩容,resize方法中发生了什么呢?

    final Node<K,V>[] resize() {
        //1. 这里的table由于没有初始化所以依然为null
        Node<K,V>[] oldTab = table;
        //2. 此时oldCap的值为0
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //3. 获取旧的扩容阈值,此时threshold还没有赋值,因此它的值为默认值0
        int oldThr = threshold;
        int newCap,newThr = 0;
        //如果旧的table中有元素,显然这里不符合条件
        if (oldCap > 0) {
           //忽略无关代码
        } 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) {
       //5. threshold变成了table数组长度与加载因子的积
            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; 
          //忽略无关代码

        return newTab;
    }

注意:这里出现了一个变量threshold,threshold是用来判断table是否需要扩容的阈值,当table中的元素数量大于threshold时table就会进行扩容。

从黄色高亮的代码中可以清楚的看到,resize方法以DEFAULT_INITIAL_CAPACITY为数组大小创建了一个新的数组替换旧的数组。

并将扩容阈值设置为DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY 也就是16*0.75 = 12

?

2.2 指定初始容量的构造函数:public HashMap(int initialCapacity)

    public HashMap(int initialCapacity) {
        this(initialCapacity,DEFAULT_LOAD_FACTOR);
    }

源码中可以看到该构造函数调用了指定初始容量和加载因子的构造函数并将加载因子指定为默认值(0.75)。

2.3? 指定初始容量和加载因子的构造函数:public HashMap(int initialCapacity,float loadFactor)

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

源码中首先对initialCapacity和loadFactor进行了验证,之后设置了扩容阈值threshold的值,(请记住这个变量,待会初始化table数组时还会用到它)

tableSizeFor方法是如何设置threshold的值的呢?这个方法的详细解释在另一篇随笔里:HashMap源码__tableSizeFor方法解析

现在我们只需要知道这个方法会返回一个不小于参数cap的最小的2的整数次幂的数,比如cap为10,就返回16、cap为17,就返回32。

table数组的初始化

通过对以上构造函数源码的解析发现,用于储存元素的table数组都没有在构造函数里初始化,那么table数组什么时候进行初始化呢? 答案就时调用put方法的时候!前面结束无参构造方法时提到过,put方法调用了putVal方法,而putVal方法对table数组进行了扩容,因此下面直接来看resize方法:

    final Node<K,V>[] resize() {
        //1. 这里的table由于没有初始化所以依然为null
        Node<K,V>[] oldTab = table;
        //2. 此时oldCap的值为0
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //3. 获取旧的扩容阈值,由于在构造函数里设置过threshol的值,所以这里threshol的值为不小指定容量的最小2的整数次幂
        int oldThr = threshold;         int newCap,newThr = 0;
        //如果旧的table中有元素,显然这里不符合条件
        if (oldCap > 0) {
           //忽略无关代码

      //4. 重点来了, 由于oldThr已经赋值了,所以会执行方法体中的newCap=oldThr,这样一来table数组的容量就变成了前面提到的threshol的值 } 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) {
       //5. threshold变成了table数组长度与加载因子的积
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>[]) new Node[newCap]; table = newTab; //忽略无关代码 return newTab; }

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读