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

文章目录
  1. 1. List
    1. 1.1 ArrayList
    2. 1.2 LinkedList
    3. 1.3 CopyOnWriteArrayList
  2. 2.Map
    1. 2.1 HashMap
    2. 2.2 LinkedHashMap
    3. 2.3 ConcurrentHashMap
    4. 2.4 TreeMap
  3. 3. Set
  4. 4. 小结
  5. 5. 系列博文
  • II. 其他
    1. 1. 一灰灰Blog: https://liuyueyi.github.io/hexblog
    2. 2. 声明
    3. 3. 扫描关注
  • 每天一个实战小技巧:容器的初始化大小指定

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

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

    1. List

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

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

    接下来逐一进行说明

    1.1 ArrayList

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

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

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

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

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

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

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

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

    1.2 LinkedList

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

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

    1.3 CopyOnWriteArrayList

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

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

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

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

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

    1
    2
    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的工具类来完成这个目的

    1
    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. 一灰灰Bloghttps://liuyueyi.github.io/hexblog

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

    2. 声明

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

    3. 扫描关注

    一灰灰blog

    QrCode

    评论

    Your browser is out-of-date!

    Update your browser to view this website correctly. Update my browser now

    ×