实战小技巧8:容器的初始化大小指定

1,703 阅读4分钟

这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战

每天一个实战小技巧:容器的初始化大小指定

容器可以说是我们日常开发中,除了基本对象之外,使用最多的类了,那么平时在使用的时候,是否有主意到良好编程习惯的大佬,在创建容器的时候,一般会设置size;那么他们为什么要这么干呢?是出于什么进行考量的呢?

今天我们将针对最常见的List/Map/Set三种容器类型的初始化值选择,进行说明

1. List

列表,在我们日常使用过程中,会接触到下面几个

  • ArrayList: 最常见的数组列表
  • LinkedList: 基于链表的列表
  • CopyOnWriteArrayList: 线程安全的数组列表

接下来逐一进行说明

1.1 ArrayList

现在以ArrayList为例,进行源码分析,当我们不指定列表大小,直接创建时

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

上面是内部实现,其中elementData就是列表中存数据的数组,初始化为默认数组

当我们第一次添加一个元素时,发现数组为默认值,会触发一次数组扩容,新的数组大小为10 (详情看源码)

其次就是数组的库容机制,通过源码/网上分享知识点可以知道,这个扩容的实现如下

  • 当新添加的元素,数组放不下时,实现扩容
  • 扩容后的大小 = 扩容前大小 + max(添加元素个数, 1/2 * 扩容前大小)

基于上面的知识点,大致可以得出指定列表长度的好处

  • 节省空间(用多少申请多少,避免浪费)
  • 减少扩容带来的拷贝(扩容一次就会带来一次数组拷贝,如果已知列表很大,结果还使用默认的10,这会产生很多可避免的扩容开销)

1.2 LinkedList

基于链表的列表,不同于上面的数组列表,它没有提供指定大小的构造方法,why?

因为链表本身的数据结构的特点,它就像糖葫芦一样,一个串一个,有数据,才有接上的可能,因此不需要指定大小

1.3 CopyOnWriteArrayList

这个又非常有意思,它同样不能指定大小,但是原因与前面不同,主要在于它保证线程安全的实现方式

  • 每次新增/修改(加锁,保证单线程访问),都是在拷贝的数组操作;完成之后,用新的替换旧的

所以说,每次变更,都会存在数组拷贝,因此就没有必要提前指定数组大小

那么它的初始化每次都使用默认的么?

并不是这样的,当我们已知这个列表中的值时,推荐使用下面这种方式

List<String> values= Arrays.asList("12", "220", "123");
List<String> cList = new CopyOnWriteArrayList<>(values);
  • 将初始化值,放在一个普通的列表中,然后利用普通列表来初始化CopyOnWriteArrayList

2.Map

常见的map容器使用,大多是下面几个

  • HashMap
  • LinkedHashMap: 有序的hashmap
  • TreeMap: 有序的hashmap
  • ConcurrentHashMap: 线程安全的map

2.1 HashMap

HashMap的底层数据结构是 数组 + 链表/红黑树,关于这个就不细说了

我们在初始化时,若不指定size,则数组的默认长度为8(请注意,Map的数组长度是2的倍数)

与ArrayList的扩容时机不一样的是,默认情况下,Map容量没满就会触发一次扩容

默认是数量达到 size * 0.75(0.75为扩容因子,可以在创建时修改),就会触发一次扩容

why?

  • 主要是为了减少hash冲突

同样的为了减少冲突,在初始化时,我们需要指定一个合适大小

比如我们

  • 已知map的数量为2,这个时候Map的大小选择因该是4
  • map数量为6,这个时候Map的大小选择是16

有时候让我们自己来计算这个值,就有些麻烦了,这个时候,可以直接使用Guava的工具类来完成这个目的

Map<String, String> map = Maps.newHashMapWithExpectedSize(6);

2.2 LinkedHashMap

初始化方式同上,略

2.3 ConcurrentHashMap

初始化方式同上,略

2.4 TreeMap

不同于上面几个的是treeMap,没有提供指定容器大小的构造方法

原因和前面说到的LinkedList有些类似,TreeMap的底层数据结构为Tree,所以新增数据是挂在树的一个节点下面,无需指定容量大小

3. Set

集合用的最多应该就是HashSet了,底层结构模型复用,所以初始化大小指定与HashMap一致,也不需要多说

4. 小结

今天这篇博文主要介绍的是三种常见的容器,在创建时,如何指定容量大小

首先明确一点,指定容量大小是为了

  • 减少扩容带来的额外开销
  • 指定容量代销,可以减少无效的内存开销

初始化值设置的关键点:

  • ArrayList: 数据有多少个,初始化值就是多少
  • HashMap: 考虑到扩容因子,初始化大小 = (size / 0.75 + 1)

5. 实战系列推荐

II. 其他

1. 一灰灰Blogliuyueyi.github.io/hexblog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

  • 微博地址: 小灰灰Blog
  • QQ: 一灰灰/3302797840
  • 微信公众号: 一灰灰blog