合理设置 HashMap 初始值大小

码农UP2U

共 1560字,需浏览 4分钟

 · 2022-05-09


早期文章


        在 Java 开发中少不了使用 HashMap,但是通常使用 HashMap 时就是简单的进行 new 一下就可以开始使用了。比如这样:

HashMap param = new HashMap<>();

        这样使用并不会有什么问题,但是如果在创建 HashMap 时如果可以预估集合的大小时,可以给 HashMap 指定一个大小。


HashMap 的底层结构

        HashMap 在底层使用了数组、链表及红黑树这几种数据结构,其中数组作为其基本的存储结构,而链表和红黑树则是用来解决 哈希冲突 的一种方法。这里只关心数组这部分。

        数组是一片连续的内存空间,初始化后大小无法改变。而数组作为 HashMap 的底层数据结构,那么当数据的数量超出数组的长度时,仍然想要往其中存入数据时,就会对数组进行“扩容”。所谓的扩容就是重新开辟一块长度更长的内存作为当前 HashMap 的底层数据结构,然后将数据复制到新的空间当中。

        由此可以看出,HashMap 的扩容会给程序带来性能上的损耗。


HashMap 的扩容

        关于HashMap 的扩容,有几个相关的属性,这些属性决定了 HashMap 的扩容时机,属性分别如下:

capacity:表示 HashMap 的容量,即实际数组的长度,该长度默认为 16,也就是说在实例化 HashMap 时没有指定它的容量时,它的容量默认为 16,代码如下:

// Constructs an empty HashMap 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,且 loadFactor 的值为 0.75。

loadFactor:表示 HashMap 的扩容因子(加载因子、负荷因子),它确定了 HashMap 进行扩容的一个比例。该扩容因子的默认值为 0.75。

threshold:扩容的阈值,该值是 capacity * load factor 计算得到值。根据默认的 capacity 和 loadFactor 得知,16 * 0.75 = 12,那么当数组内的元素大于 12 时则进行扩容。在 HashMap 中有一个属性 size,用来表示数组的大小,通常通过 size() 方法获取。HashMap 的扩容是当前容量乘以 2 的大小。


代码演示扩容的实际情况

        通过一段代码来验证一下 HashMap 在默认容量情况下的一次扩容。测试代码如下:

public class HashMapTest{    public static void main(String[] args) throws Exception    {        HashMap<String, Integer> map = new HashMap<>();
for (Integer i = 1; i <= 16; i ++) { map.put(i.toString(), i); System.out.println("i = " + i + ", map size = " + map.size() + ", map capacity = " + tableLength(map)); } }
public static int tableLength(HashMap map) throws Exception { Field table = HashMap.class.getDeclaredField("table"); table.setAccessible(true); Object[] obj = (Object[]) table.get(map);        return obj.length; }}

        来观察它的输出情况,输出如下:

i = 1, map size = 1, map capacity = 16i = 2, map size = 2, map capacity = 16i = 3, map size = 3, map capacity = 16i = 4, map size = 4, map capacity = 16i = 5, map size = 5, map capacity = 16i = 6, map size = 6, map capacity = 16i = 7, map size = 7, map capacity = 16i = 8, map size = 8, map capacity = 16i = 9, map size = 9, map capacity = 16i = 10, map size = 10, map capacity = 16i = 11, map size = 11, map capacity = 16i = 12, map size = 12, map capacity = 16i = 13, map size = 13, map capacity = 32i = 14, map size = 14, map capacity = 32i = 15, map size = 15, map capacity = 32i = 16, map size = 16, map capacity = 32

        从上面的情况可以看出,在 HashMap 中存入第 13 个数据时,capacity 的值变为了 32。


实例化 HashMap 设置初始化大小

        在使用 HashMap 时,我们有时会用它传递参数,或者返回一些值。在这种类似的情况下,我们在实例化 HashMap 时可以为其设置一个初始值。设置初始值,可以 HashMap 在初始化时不必开辟过多的内存,也可以避免不断的进行扩容。


        比如,我们使用 HashMap 传参时,可能固定的传递 4 个参数,那么 HashMap 默认使用 16 个容量,就显得有点多了。那么如果 HashMap 有 4 个值,应该设置为多少呢。只需要使用 值的个数 除以 loadFactor,然后向上取整即可。比如 4 除以 0.75 等于 5.3,此时我们初始化大小为 6 即可。我们进行测试。代码如下:

public static void main(String[] args) throws Exception{    HashMap map = new HashMap<>(6);
for (Integer i = 1; i <= 4; i ++) { map.put(i.toString(), i); System.out.println("i = " + i + ", map size = " + map.size() + ", map capacity = " + tableLength(map)); }}

        输出内容如下:

i = 1, map size = 1, map capacity = 8i = 2, map size = 2, map capacity = 8i = 3, map size = 3, map capacity = 8i = 4, map size = 4, map capacity = 8

        此时,可以看到 HashMap 并没有发生扩容,但是它的容量也不是我们指定的 6,因为 HashMap 会对我们的初始容量找一个大于它的、2 的幂次的值。那么比 6 大的,且是 2 的幂次的值就是 8,8 是 2 的 3 次幂。


总结

        本文介绍了关于 HashMap 中影响其扩容的几个属性,通过这几个属性也了解了如何合理的设置 HashMap 初始值的大小。希望对你有所帮助。


公众号内回复 【mongo】 下载 SpringBoot 整合操作 MongoDB 的文档。

公众号内回复 【cisp知识整理】 下载 CISP 读书笔记。

公众号内回复【java开发手册】获取《Java开发手册》黄山版。






浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报