zoukankan      html  css  js  c++  java
  • HashMap的put kv,是如何扩容的?

    HashMap的put kv,是如何扩容的?

    描述下HashMap put(k,v)的流程?
    它的扩容流程是怎么样的?

    HashMap put(k,v)流程

    1. 通过hash(key方法)获取到key的hash值
    2. 调用put方法, 将value存放到指定的位置
      1. 根据hash值确定当前key所在node数组的索引 (n - 1) & hash
      2. 如果node[i]==null 则直接创建新数组
      3. 如果node[i]!=null
        1. 判断 当前node的头结点的 hash和key是否都相等, 相等则需要操作的就是该node
        2. 判断当前节点是否为TreeNode,对TreeNode进行操作,并返回结果e
        3. 如果是链表则遍历链表,key存在则返回节点e,不存在则赋值
        4. 判断节点e有没有被赋值,覆盖旧值
      4. hashMap size进行加1,同时判断v新size是否大于扩容阈值从而判断是否需要扩容
    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数组tab, Node节点
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            // 对tab数组赋值为当前HashMap的table, 并判断是否为空, 或者长度为0
            // 为0进行则resize()数组, 并对 n赋值为当前tab的长度
            // resize() 对HashMap的table扩容, 并返回扩容后的新数组
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            // 对 node p 进行赋值, 数组所在位置 即 node p 如果是null 则直接赋值
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                // p 不为null, 声明 node e, key k
                Node<K,V> e; K k;
                // 如果hash值相等且key相等, 直接将 e 赋值为当前node的头节点
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                else if (p instanceof TreeNode)
                // 如果是红黑树, 则对树进行操作, 返回节点e
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else {
                    // 对链表进行遍历, 找到对应的节点
                    for (int binCount = 0; ; ++binCount) {
                        // 将 e 赋值为  头节点p的next, 如果下一个节点为null
                        if ((e = p.next) == null) {
                            // 对节点进行赋值
                            p.next = newNode(hash, key, value, null);
                            // 如果长度到达数转换阈值, 则需要转换为红黑树
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        // 如果e节点的hash相等, key相等, 则 直接跳出循环 e 已经被赋值为 p.next
                        // 此时e节点的value没有被赋值
                        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()扩容过程

    1. JDK 1.7 扩容流程, 每次都需要数组扩容后, 链表需要重新计算在新数组的位置
    2. JDK 1.8 不需要重新计算 (优化点)
      1. 数组下标: (n - 1) & hash 即数组长度-1 & key的hash
      2. 扩容后的数组下标: ((n << 1) - 1) & hash 相当于在 高位1之前加了个1

    如图所示, 真正发生影响的是新增的那一位(红色箭头所指), 所以 oldCap & hash 完全可以判断该值是放在旧索引值的位置还是放在旧索引值+旧数组长度的位置

    
    final Node<K,V>[] resize() {
        // 旧数组
        Node<K,V>[] oldTab = table;
        // 旧数组长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // 旧的扩容阈值
        int oldThr = threshold;
        // 新的数组长度和新扩容阈值
        int newCap, newThr = 0;
        // 旧数组存在
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 新数组长度为旧数组长度的2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                // 扩容阈值是旧扩容阈值的2倍
                newThr = oldThr << 1; // double threshold
        }
        // 旧数组不存在, 相当于首次put(K, V)时, 将数组长度置为扩容阈值
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            // 旧数组不存在, new HashMap()未指定长度, 初次put(K, V), 设置为默认值
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 新的扩容阈值是0, 则将扩容阈值设置为 新数组长度*负载因子
        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"})
        // 创建新数组, 长度为新长度, 即原数组长度的2倍
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        // 将table复制为新数组
        table = newTab;
        if (oldTab != null) {
            // 对旧数组进行遍历
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                // 旧节点node赋值
                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节点
                            next = e.next;
                            // 节点hash与旧数组长度 & 的结果来决定元素所在位置, 参考上面图示所讲
                            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;
                            // 索引j处直接赋值
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            // 索引 j + 老数组长度位置存放hiHead
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
    
    
  • 相关阅读:
    codeforces C. No to Palindromes!
    codeforces D. Pashmak and Parmida's problem
    codeforces C. Little Pony and Expected Maximum
    codeforces D. Count Good Substrings
    codeforces C. Jzzhu and Chocolate
    codeforces C. DZY Loves Sequences
    codeforces D. Multiplication Table
    codeforces C. Painting Fence
    hdu 5067 Harry And Dig Machine
    POJ 1159 Palindrome
  • 原文地址:https://www.cnblogs.com/liuzhihang/p/hashmap2.html
Copyright © 2011-2022 走看看