hashmap原理解析

  • 扩容机制

    • 何时扩容
      当元素个数size>threshold=cap*loadFactor
      size指的是key value的元素个数, cap指hashmap的table数组的长度(不包括链表),loadFactor指负载因子,默认0.75
    • 扩容关键参数
      cap,threshold,size
      默认初始大小16,负载因子0.75
    capthresholdsize
    16120
    322413
    644825
    1289649
    25619297
    512384193
    1024768385
    20481536769
    409630721537
    819261443073
    16384122886245

    当size>threshold的时候,会发生扩容,扩容之后的容量cap是上一次的两倍,threshold也是上一次的两倍,由于都是两倍,所以threshold=cap*loadFactor这个等式在扩容后仍然成立.

    • 元素个数和初始容量的公式
      当已知map元素个数是x的情况下,如何设置初始化容量?
      HashMap map = new HashMap(int initialCapacity);
      由于threshold=cap乘以loadFactor<元素个数的时候会扩容,所以为了不扩容,必须满足:
      所以 threshold=cap*loadFactor>=元素个数
      所以 cap>=元素个数/loadFactor
      cap是大于或者等于(元素个数/loadFactor)的最小的2的n次幂
      综上所述:
      当负载因子是0.75的时候,
      元素个数和初始化参数有如下关系:
元素个数threshold初始化个数
1317.717-32
9612865-128
1000013333.38193-16384

这个公式比较难表达出来
initialCapacity的最大值是大于或者等于(元素个数/loadFactor)的最小的2的次方.假设这个数是maxInitialCapacity
initialCapacity的最小值是maxInitialCapacity/2+1;
initialCapacity是maxInitialCapacity/2+1的时候,数组的初始大小会自动扩展到maxInitialCapacity;

  • 阿里规约
    initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即loader factor)默认为0.75, 如果暂时无法确定初始值大小,请设置为16(即默认值)。

  • 谷歌guava工具类
    com.google.common.collect.Maps#newHashMapWithExpectedSize

        public static <K, V> HashMap<K, V> newHashMapWithExpectedSize(int expectedSize) {
            return new HashMap(capacity(expectedSize));
        }
    
        static int capacity(int expectedSize) {
            if (expectedSize < 3) {
                CollectPreconditions.checkNonnegative(expectedSize, "expectedSize");
                return expectedSize + 1;
            } else {
                return expectedSize < 1073741824 ? (int)((float)expectedSize / 0.75F + 1.0F) : 2147483647;
            }
        }
    

    如果用Maps#newHashMapWithExpectedSize,expectedSize指定为元素个数即可

  • 扩容过程解析,java7 vs java8

    • java7扩容的时候会遍历table的Entry,每个Entry里再遍历链表.由于扩容的时候,hashCode不变,但是hashSeed发生变化,java7的hash依赖于hashCode和hashSeed,所以java7每个节点的hash都要重新计算,并且链表的节点顺序会逆转.
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}
  • java8扩容的时候也是遍历table的Entry,每个Entry里再遍历链表.但是java8的hash只依赖于hashCode,扩容的时候hashCode不变,所以hash不变,不需要重新计算.同时java8做结点重排序的时候有一个巧妙的算法.即结点的hash不变,newCap=oldCap*2的时候,结点X在新map中的位置要么等于原来的位置p,要么等于p+oldCap,判断条件是(e.hash & oldCap) == 0是否满足,满足则在原位置p,不满足则在p+oldCap
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;
            }
        }
    }
}

具体的分析可参考:https://blog.csdn.net/qq_37113604/article/details/81353626

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值