Array List
[TOC]
ArrayList
类图

数据结构
数组:Object[] elementData
原理
扩容
知识了解
minCapacity:最小扩容量,分析源码不难知道,等于当前数组的size+1。
首次扩容
当我们使用ArrayList()无参构造器初始化对象的时候,默认不扩容,将默认的空数组赋值给数组。后续,在新增数据的时候,触发了扩容机制。
其中,扩容的大小取默认容量、最小扩容量的最大值,之后扩容逻辑同非首次扩容。
非首次扩容
源码处理流程:查看大图
源码分析:
JDK版本区别
1.7和1.8最大的区别就是:1.7是饿汉式创建集合容量,1.8是懒汉式创建集合容量
优缺点
优点
1、因为是数组,所以根据脚标取数据很快
缺点
1、数组的新增、删除操作效率不高,涉及到数组的扩容、复制等操作 2、线程不安全
拓展
以下代码,均基于jdk1.8
如何证明线程不安全?
在不运行之前,你觉得结果是如何?list.size()为10000吗。实际运行结果,每次都是不一样的, 且会出现ArrayIndexOutOfBoundsException异常,正是因为ArrayList没有保证线程安全,在并发访问时存在线程安全问题。
那么为什么会存在线程安全问题呢?这还得归根到源码里:
size++是非原子操作,在并发时,会有线程安全。
size++可拆解为size=size+1,细分为:第一步,计算size + 1 ,第二步,把size + 1的结果赋值给size,在执行第二步的时候,可能size的值已经发生了变化。
那么怎么简单解决呢?使用synchronized + volatile即可,当然了还有其他的方式,本篇文章不在赘述,在并发编程相关内容,将会详细研讨。
扩容了几次?
看看以下代码,猜猜一共扩容了几次?
为了方便了解到具体扩容了几次,我们使用反射的机制获取ArrayList中的elementData数组一看便知:
运行结果:
结合结果,可见一共扩容了7次,分别是添加1、2、3、4、5、7、10的时候,elementData数组大小变化为:0(new结束)、1、2、3、4、6、9、13。
没猜对?没关系,咱们在稍微变化下题目:
这次是几次呢?答案:稍后揭晓。
首先我们来回顾下两种情况的不同点——构造器:
很明显针对构造器来讲,区别就是初始赋值不一样,至于为啥不一样,源码解释了:
EMPTY_ELEMENTDATA:
Shared empty array instance used for empty instances.DEFAULTCAPACITY_EMPTY_ELEMENTDATA:
Shared empty array instance used for default sized empty instances. We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.
我们暂且将这两种情况称为实际空实例(即人为设置size=0)、默认空实例(即使用空造器)。看完了构造器,我们在来回顾下add的扩容机制:
也就是说,针对默认空实例、实际空实例处理是不一样的,默认空实例扩容直接使用默认的大小10,两者后续扩容都按照1.5倍的机制来扩容。
说到这里,第二种的情况,答案也有了:一共扩容了1次,添加1的时候,elementData数组大小变化为:0(new结束)、10。
至于为什么要谈这两道题目,一方便,是为了更好的理解jdk为ArrayList做的优化;另一方面,是避免小伙伴有new ArrayList<>(0);的习惯,以为节省了内存,实则可能会产生多次扩容的时间浪费。
Last updated