使用 HashMap 存一万条数据,构造时传 10000 还会触发扩容吗?

简介: 向HashMap 中存10000 条数据,初始化时,构造方法传值10000,会触发扩容吗?

问题


  HashMap 中存10000 条数据,初始化时,构造方法传值10000,会触发扩容吗?

Map<String,String> map = new HashMap<>(10000);


分析

乍一看

  肯定会触发扩容呀,因为 HashMap 中有个负载因子默认为0.75,就是说存储的数量超过容量的 75% 就会触发扩容,put 到后 25% 的数据时,肯定就会触发扩容。但事实真是这样吗?源码中有我们想知道的一切,真相只有一个。


分析源码

HashMap的初始化

   HashMap 中,提供了一个指定初始容量的构造方法HashMap(int initialCapacity),这个方法再通过 DEFAULT_LOAD_FACTOR 调用 HashMap 另一个构造方法,初始化 loadFactor 0.75

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
    ......
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}


  构造方法初始化了两个成员变量 threshold loadFactor,其中 threshold 就是用来存储触发 HashMap 扩容的阈值,也就是说,当 HashMap 存储的数据量达到 threshold 时,就会触发扩容。
  
从改造方法中可以看出,threshold 并没有直接使用传入的 initialCapacity 作为扩容阈值,而是通过 tableSizeFor 方法处理后再赋值给 threshold

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

  这啥呀?看不懂也没关系,我来解释。tableSizeFor的作用就是寻找大于cap 2 的整数次方,例如如果传入 10,经过 tableSizeFor 处理后返回 16。至于为什么这样做,不在此处详细讨论。


回归正题


  还有一个问题,一直在说 threshold 是触发扩容的阈值,即 (initialCapacity * loadFactor),但是我们再构造方法中使用 tableSizeFor 初始化 threshold,并没有用到 loadFactor,其实这一步被移交给resize方法实现了,因为我们并没有在构造方法中初始化哈希表数组,扩容阈值 threshold = initialCapacity * loadFactor,这一步在 resize 中完成。
  
如果我们从外部传递进来 10000 初始化 initialCapacity ,实际上经过 tableSizeFor 方法处理之后,最终 threshold 就会变成 2 14 次幂 16384,再在 resize 方法中乘上负载因子 0.75,实际在不触发扩容的前提下,可存储的数据容量是 12288(16384 * 0.75),用来存放 10000 条数据,绰绰有余,所以并不会触发扩容。


initialCapacity


  关于如何选择 initialCapacity,我们看看阿里巴巴 Java 开发规范,规范要求在初始化HashMap 时,必须指定initialCapacity,因为这样可以减少 resize 次数,提高程序效率。因为 threshold = initialCapacity * loadFactor,所以 initialCapacity = (需要存储元素个数 / loadFactor) + 1


示例


  想要使用 HashMap 存放 10000 条数据,应该设置 initialCapacity = 10000 / 0.75 + 1 = 13334,然后哈希表容量会被 tableSizeFor 方法调整到 16384(2^14)threshold = 16384 * 0.75 = 12288 足够存储 10000 条数据而不会触发扩容。


总结

  • HashMap 构造方法传递的 initialCapacity 实际表示哈希表的容量,不代表扩容阈值。
  • 构造方法传递的     initialCapacity,会被 tableSizeFor 方法调整为大于它的 2 的整数次方。
  • 如果使用     initialCapacity 进行初始化,HashMap 是否扩容,由 threshold 决定,扩容阈值 threshold = initialCapacity * loadFactor
  • 在初始化 HashMap 时,必须指定     initialCapacity 来提升效率,initialCapacity = (需要存储元素个数 /      loadFactor(默认0.75)) + 1
相关文章
|
6月前
|
存储 Java
HashMap扩容机制详解
HashMap扩容机制详解
|
30天前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
52 5
|
30天前
|
算法 索引
让星星⭐月亮告诉你,HashMap的resize()即扩容方法源码解读(已重新完善,如有不足之处,欢迎指正~)
`HashMap`的`resize()`方法主要用于数组扩容,包括初始化或加倍数组容量。该方法首先计算新的数组容量和扩容阈值,然后创建新数组。接着,旧数组中的数据根据`(e.hash & oldCap)`是否等于0被重新分配到新数组中,分为低位区和高位区两个链表,确保数据迁移时的正确性和高效性。
63 3
|
30天前
|
存储 索引
让星星⭐月亮告诉你,HashMap在put数据时是如何找到要存放的位置的?
HashMap 是一种常用的键值对存储结构,其底层采用数组+链表+红黑树实现。本文探讨了 HashMap 在插入键值对时如何确定存放位置。通过分析 `put` 方法的源代码,重点解析了哈希码的计算过程和数组索引的确定方法。哈希码通过 `hashCode()` 方法和位运算优化,确保均匀分布,从而减少哈希碰撞,提高性能。最终,通过 `(n-1) & hash` 计算出数组索引,确保键值对被正确存放到指定位置。
33 2
|
30天前
|
算法 索引
HashMap扩容时的rehash方法中(e.hash & oldCap) == 0算法推导
HashMap在扩容时,会创建一个新数组,并将旧数组中的数据迁移过去。通过(e.hash & oldCap)是否等于0,数据被巧妙地分为两类:一类保持原有索引位置,另一类索引位置增加旧数组长度。此过程确保了数据均匀分布,提高了查询效率。
37 2
|
2月前
|
设计模式 安全 Java
HashMap底层原理:数据结构+put()流程+2的n次方+死循环+数据覆盖问题
假如有T1、T2两个线程同时对某链表扩容,他们都标记头结点和第二个结点,此时T2阻塞,T1执行完扩容后链表结点顺序反过来,此时T2恢复运行再进行翻转就会产生环形链表,即B.next=A;采用2的指数进行扩容,是为了利用位运算,提高扩容运算的效率。JDK8中,HashMap采用尾插法,扩容时链表节点位置不会翻转,解决了扩容死循环问题,但是性能差了一点,因为要遍历链表再查到尾部。例如15(即2^4-1)的二进制为1111,31的二进制为11111,63的二进制为111111,127的二进制为1111111。
HashMap底层原理:数据结构+put()流程+2的n次方+死循环+数据覆盖问题
|
6月前
|
存储 安全 算法
【JAVA】HashMap扩容性能影响及优化策略
【JAVA】HashMap扩容性能影响及优化策略
|
5月前
|
存储 安全 Java
深入解析Java HashMap的高性能扩容机制与树化优化
深入解析Java HashMap的高性能扩容机制与树化优化
145 1
|
5月前
|
索引
HashMap扩容为什么每次都是之前的2倍
HashMap扩容为什么每次都是之前的2倍
83 0