代码优化 | 如何设置HashMap的初始值大小

写在前面

通过本文可以了解 HashMapArrayList 的扩容策略以及如何设置初始值大小。

为什么要设置初始值大小?

底层使用数组实现的集合类,比如:HashMapArrayList,都会存在扩容的问题,当数组空间不够用时,需要对数组进行动态扩容,不然就会造成 ArrayIndexOutOfBoundsException 异常。

如果在创建集合时,就已经知晓集合的大小,那么就可以事先设置集合底层数据的大小,这样就可以避免添加元素的过程中自动扩容,从而达到提高性能的目的。

HashMap 为例,已知集合有两个元素待加入,那么下面的代码有没有问题?

public static void main(String[] args) throws Exception {
    HashMap<String, Integer> map = new HashMap<>(2);
    map.put("1", 1);
    map.put("2", 1);
}

可以打印一下数组长度,看看过程中 HashMap 有没有自动进行扩容

public class ListsTest {
    public static void main(String[] args) throws Exception {
        HashMap<String, Integer> map = new HashMap<>(2);
        map.put("1", 1);
        displayMapLength(map);
        map.put("2", 1);
        displayMapLength(map);
    }

    public static void displayMapLength(HashMap<?, ?> map) throws Exception {
        Field field = HashMap.class.getDeclaredField("table");
        field.setAccessible(true);
        Object[] elementData = (Object[]) field.get(map);
        System.out.println(elementData == null ? 0 : elementData.length);
    }
}

输出结果如下,结果很明显,进行了一次扩容。

2
4

HashMap 的初始容量设置为期望集合大小是不对的,会导致扩容,那么应该设置为多少才合适呢?这就需要来了解一下 HashMap 的扩容机制。

扩容机制

几个概念:

  • capacity:容量,HashMap 的内部实现使用数组进行数据存储,capacity 就是数组的长度,默认为 16
  • size:容器内已存储数据的数量
  • loadFactor:扩容因子,一个 0 - 1 之间的数值,默认为 0.75
  • threshold:扩容界限,threshold = capacity * loadFactor 向下取整

扩容策略:

向容器新增一个数据,size 加 1,当 size > threshold 时,执行扩容,capacity = capacity * 2 每次扩容为原来的一倍。

在这里插入图片描述

用上面的例子再验证一下扩容机制

初始态:

capacity = 2
threshold = 2 * 0.75 = 1

新增第一个元素,size = 1size > threshold 不满足,不执行扩容。
新增第二个元素,size = 2size > threshold 满足条件,执行扩容,扩容后

capacity = capacity * 2 = 4
threshold = 4 * 0.75 = 3

如果继续向容器新增数据,在新增第 4 个元素时会导致下一次扩容。

如何设置初始值

了解了扩容机制后,现在回到开始的问题,已知集合有两个元素待加入,HashMap 的容量初始化为多少最合适?

只需要满足 capacity * 0.75 >= expectedSize 即可,所以容量设置可以使用如下公式

capacity = (int) Math.ceil((float) expectedSize / 0.75F)

expectedSize = 2 时,使用公式计算得到 capacity = 3,再重新跑一遍之前的程序

public class ListsTest {
    public static void main(String[] args) throws Exception {
        HashMap<String, Integer> map = new HashMap<>(3);
        map.put("1", 1);
        displayMapLength(map);
        map.put("2", 1);
        displayMapLength(map);
    }
}

结果输出为

4
4

很明显,没有进行扩容,但我们明明设置的是 3,但实际容量却是 4,这是因为 HashMap 在构造函数中对容量进行多一次的计算,计算后的容量一定是 2n

如何设置 ArrayList 初始值

ArrayList 的内部实现与 HashMap 是不一样的,扩容机制也不一样。

  • ArrayList 初始化时,设置容量为多少,数组长度即为多少,不会进行多一次计算。
  • ArrayList 没有 loadFactor 扩容因子,数组长度不够,才进行扩容。
  • ArrayList 每次扩容,增加原来的一半容量。

所以,如果有两个元素待加入列表,那么直接创建一个初始容量为 2 的列表即可。

Lists & Maps 工具类

如果觉得自己去理清这些东西比较麻烦的话,有人已经帮我们把这些都考虑到了,使用现成的工具类即可解决这些问题。

Guava 提供的工具类中,ListsMaps 中提供了相应的 API

Map<String, Integer> map = Maps.newHashMapWithExpectedSize(2);
List<String> list = Lists.newArrayListWithCapacity(2);
  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值