• JDK1.8 hashMap源码分析


    默认初始容量为16

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    最大容量为2^30

    static final int MAXIMUM_CAPACITY = 1 << 30;

    默认负载因子为0.75f

    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    THRESHOLD 阀值,临界值,hashmap实际容量达到阀值后进行扩容。

    hashMap的构造函数
    1.无参构造,使用默认的初始容量16,默认的负载因子0.75f
       public HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        }

      2.指定初始容量

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

      3.指定初始容量和负载因子,如果指定的初始容量大于支持的最大容量2^30次方则重设初始容量为2^30次方。

        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);  // jdk1.7此处 threshold = initialCapacity;阀值直接等于初始容量,会在第一次put时重设阀值。
        }

      然后设置阀值

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

    对设置的初始容量加一后进行一系列的右移然后或运算,如设置初始容量为9,结果为16,设置初始容量为16,则结果为16。也就是说找到 小于等于(n-1)*2的最大的2的次方的值。

    所以如果不指定初始容量,则初始容量和阀值都为16。

      4.直接根据接收一个map的构造函数创建map

        public HashMap(Map<? extends K, ? extends V> m) {
            this.loadFactor = DEFAULT_LOAD_FACTOR;
            putMapEntries(m, false);
        }

    put方法,才初始化node数组

        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;
         // transient Node<K,V>[] table; 如果node数组没有初始化则进行一个resize。
    if ((tab = table) == null || (n = tab.length) == 0)
          //对数组执行扩容操作。 n
    = (tab = resize()).length;
          // 对数组长度减一和key的hash值进行与运算得到数组下标,查询此下标是否有值。
    if ((p = tab[i = (n - 1) & hash]) == null)
          // 没值就新建一个node将key,value放在此下标上 tab[i]
    = newNode(hash, key, value, null); else {
          // 运算得到的数组对应的下标已经有值了,则判断已经存在的值的key和将要保存的key是否都相同 Node
    <K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
              // 如果相同,则将旧值p赋给e e
    = p; 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,value放在链接的新的节点上 p.next
    = newNode(hash, key, value, null);
                  // TREEIFY_THRESHOLD =8,如果链表的长度为8,则转为红黑树。
    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; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }

    resize方法:在第一次put元素的时候就执行一次。final Node<K,V>[] resize()         Node<K,V>[] oldTab = table;

         //第一次put时table为null
            int oldCap = (oldTab == null) ? 0 : oldTab.length;  // 旧的node数组的长度
            int oldThr = threshold;  // 旧的阀值
            int newCap, newThr = 0; 
            if (oldCap > 0) {
          // 如果旧的数组的长度大于2^30次方,则旧的阀值为int型的最大值即为2^31次方
    if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; }
            // 新的数组的长度为旧的数组长度的两倍,如果旧的数组的长度的两倍小于最大容量2^30,且旧的数组的长度大于等于默认初始容量16,则新的阀值为旧的阀值的两倍。
    else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold }
          //如果旧的阀值大于0,且node数组没有初始化,即刚创建hashMap,且指定了初始容量。第一次put。则,新的容量等于旧的阀值
    else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; // 16 else {
    // zero initial threshold signifies using defaults
            // 即创建HashMap用的是无参构造,还未初始化数组,则设置数组长度为默认容量16,阀值为默认负载因子0.75f*16=12
    newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); }
        // 只有在用hashmap的带参构造创建map且第一次put数组没有初始化时。此时新数组的长度为创建map时的阀值。
    if (newThr == 0) {
           // 修改创建map时赋的阀值,为其自身的0.75.而这时创建的数组的长度为创建map时赋的阀值,如创建时指定了初始容量为9,则会创建一个初始容量为16的数组,后将阀值设为12.
    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; }

    创建红黑树替换之前的链表

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

    1.7和1.8的区别

    1、(无参构造不同)1.8的无参构造没有初始化阀值,在第一次put时如果阀值未初始化的话才设数组的容量为默认初始容量,阀值为容量的0.75。1.7的无参构造和带参构造都指定了了阀值等于初始容量。

    2、(带参构造的初始阀值不同)用带参构造的话1.7的阀值等于初始容量,第一次put时设为f(n)*0.75。1.8用带参构造创建map时,阀值为f(n)即小于等于(n-1)*2的最大的2的次方的值,n为指定的初始容量或为默认初始容量

    3、(数组长度的值来源不同,但结果相同)1.7和1.8都是在第一次put时初始化数组。不同的是1.7直接将数组的长度设为了f(n),将阀值设为了长度的0.75。1.8是在第一次put中的resize中初始化数组,如果是带参构造创建的map则将初始阀值设为数组的长度,再修改阀值为其自身的0.75

    4、(数据结构不同)1.7的数据结构为数组+链表。1.8的数据结构为数组+链表+红黑树。当链表的长度大于8时,将链表修改为红黑树,将原来链表数据复制进去

    5、(resize方法不同)1.8数组未初始化时也是通过resize进行初始化的

    未完待续...

  • 相关阅读:
    LeetCode 39. Combination Sum
    LeetCode 37. Sudoku Solver
    LeetCode 36. Valid Sudoku
    LeetCode 34. Search for a Range
    LeetCode 33. Search in Rotated Sorted Array
    VS2010出现灾难性错误的解决办法
    双系统下利用MbrFix.exe卸载LINUX系统
    VS 与 SQLite数据库 连接
    人月神话阅读笔记2
    关于疫情数据分析web开发2-网页爬取实现
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/10644515.html
Copyright © 2020-2023  润新知