8.容器的初始化大小指定
实战8:容器的初始化大小指定
容器可以说是我们日常开发中,除了基本对象之外,使用最多的类了,那么平时在使用的时候,是否有主意到良好编程习惯的大佬,在创建容器的时候,一般会设置size;那么他们为什么要这么干呢?是出于什么进行考量的呢?
今天我们将针对最常见的List/Map/Set三种容器类型的初始化值选择,进行说明
1. 容器初始化
1.1. List
列表,在我们日常使用过程中,会接触到下面几个
- ArrayList: 最常见的数组列表
- LinkedList: 基于链表的列表
- CopyOnWriteArrayList: 线程安全的数组列表
接下来逐一进行说明
1.1.1 ArrayList
现在以ArrayList为例,进行源码分析,当我们不指定列表大小,直接创建时
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
上面是内部实现,其中elementData
就是列表中存数据的数组,初始化为默认数组
当我们第一次添加一个元素时,发现数组为默认值,会触发一次数组扩容,新的数组大小为10 (详情看源码)
其次就是数组的库容机制,通过源码/网上分享知识点可以知道,这个扩容的实现如下
- 当新添加的元素,数组放不下时,实现扩容
扩容后的大小
=扩容前大小
+ max(添加元素个数
, 1/2 *扩容前大小
)
基于上面的知识点,大致可以得出指定列表长度的好处
- 节省空间(用多少申请多少,避免浪费)
- 减少扩容带来的拷贝(扩容一次就会带来一次数组拷贝,如果已知列表很大,结果还使用默认的10,这会产生很多可避免的扩容开销)
1.1.2 LinkedList
基于链表的列表,不同于上面的数组列表,它没有提供指定大小的构造方法,why?
因为链表本身的数据结构的特点,它就像糖葫芦一样,一个串一个,有数据,才有接上的可能,因此不需要指定大小
1.1.3 CopyOnWriteArrayList
这个又非常有意思,它同样不能指定大小,但是原因与前面不同,主要在于它保证线程安全的实现方式
- 每次新增/修改(加锁,保证单线程访问),都是在拷贝的数组操作;完成之后,用新的替换旧的
所以说,每次变更,都会存在数组拷贝,因此就没有必要提前指定数组大小
那么它的初始化每次都使用默认的么?
并不是这样的,当我们已知这个列表中的值时,推荐使用下面这种方式
List<String> values= Arrays.asList("12", "220", "123");
List<String> cList = new CopyOnWriteArrayList<>(values);
- 将初始化值,放在一个普通的列表中,然后利用普通列表来初始化
CopyOnWriteArrayList
1.2.Map
常见的map容器使用,大多是下面几个
HashMap
LinkedHashMap
: 有序的hashmapTreeMap
: 有序的hashmapConcurrentHashMap
: 线程安全的map
1.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);
1.2.2 LinkedHashMap
初始化方式同上,略
1.2.3 ConcurrentHashMap
初始化方式同上,略
1.2.4 TreeMap
不同于上面几个的是treeMap,没有提供指定容器大小的构造方法
原因和前面说到的LinkedList有些类似,TreeMap的底层数据结构为Tree,所以新增数据是挂在树的一个节点下面,无需指定容量大小
1.3. Set
集合用的最多应该就是HashSet
了,底层结构模型复用,所以初始化大小指定与HashMap一致,也不需要多说
2. 小结
今天这篇博文主要介绍的是三种常见的容器,在创建时,如何指定容量大小
首先明确一点,指定容量大小是为了
- 减少扩容带来的额外开销
- 指定容量代销,可以减少无效的内存开销
初始化值设置的关键点:
- ArrayList: 数据有多少个,初始化值就是多少
- HashMap: 考虑到扩容因子,初始化大小 =
(size / 0.75 + 1)