简介:
ArrayList
是 List
接口的一个实现类,它是程序中最常见的一种集合。
在 ArrayList 内部封装了一个长度可变的数组
对象,当存入的元素超过数组长度时,ArrayList 会在内存中分配一个更大的数组来存储这些元素,因此可以将 ArrayList 集合看作一个长度可变的数组。
正是由于 ArrayList 内部的数据存储结构是数组形式,在增加或删除指定位置的元素时,会创建新的数组,效率比较低,因此不适合做大量的增删操作。但是,这种数组结构允许程序通过索引的方式来访问元素,因此使用 ArrayList 集合在遍历和查找元素时显得非常高效。
以下我会通过一个简单的 Demo
去阅读 ArrayList 的源码,分析它为什么可以自动扩容
源码解析
结论(先给出结论便于理解):
- 1)ArrayList 中维护了一个
Object 类型
的数组elementData
- 2)当创建 ArrayList 对象时,如果使用的是无参构造器,则初始 elementData 容器为
0
,第一次添加,则扩容 elementData 为10
,如需再次扩容,则扩容 elementData 为 `1.5 倍 - 3)如果使用的是指定大小的构造器,则初始 elementData 容器为
指定大小
,如果需要扩容,则直接扩容 elementData 为1.5 倍
PS:以下源码来源于 JDK1.8
这里我写了一个测试方法,通过 debug 的方式去阅读源码,打上断点,运行
如果你是使用 IDEA
通过 Debug
去阅读源码的话,建议把箭头所指的勾选
给去掉,这样看到的信息会比较全一点。
先执行以下这行代码
// 使用无参构造创建 ArrayList 对象
ArrayList list = new ArrayList();
无参构造方法源码:
程序会走到 ArrayList
类的无参构造方法中,执行 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
这样一段代码
跟进 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,它默认是一个空的 Object[]
数组对象。
所以在使用无参构造创建 ArrayList
对象时,ArrayList
对象中的 elementData
属性初始化
的时候就是一个 Object 类型的空数组
。
接着执行以下代码往集合中添加元素
// 添加元素
list.add(1);
进入 ArrayList 类的 add
方法中
该方法中做了两件事:
- ① 确定当前集合是否需要扩容;
- ② 对数组的下一个坐标进行赋值
这里最主要的就是 ensureCapacityInternal()
这个方法,进入该方法
-
① 会先判断
elementData
是否为空集合 {}
,- 如果为空集合的话就需要确定最小容量
minCapacity
,跟进DEFAULT_CAPACITY
属性发现默认为10
方法参数minCapacity
等于size + 1
,所以说如果没有指定集合长度,在第一次添加元素的时候,集合初始化大小为 10
- 如果为空集合的话就需要确定最小容量
-
② 再进入到
ensureExplicitCapacity()
该方法中
该方法
- ①
modCount++
,modCount 是用于记录当前集合被修改的次数,它的意义就是为了防止多个线程同时去修改而出现异常
- ② 判断最小容量是否大于当前集合的容量
- 如果大于,则进入真正的扩容
grow()
方法 - 如果小于,则表示不需要扩容
- 如果大于,则进入真正的扩容
以上代码就是底层扩容的核心了,大概逻辑如下:
- ① 第一次扩容
newCapacity
=最小容量
- ② 第二次及其以后,都是按照集合大小的
1.5 倍
进行扩容 - ③ 扩容使用的是
Arrays.copyOf()
方法
grow()
方法执行完以后,回到 add()
方法中,elementData
就有空间了,如果是使用无参构造创建的集合,并且第一次添加元素,那么 elementData = [null,null,null,null,null,null,null,null,null,null]
。
接着执行是有参构造的代码
// 使用有参构造创建 ArrayList 对象,指定大小为 5
ArrayList initList = new ArrayList(5);
// 添加元素
initList.add(1);
有参构造方法源码:
这段代码的逻辑就是将 elementData 赋值为指定大小的 Object 数组对象,指定大小为负数的话就会抛出异常
之后又进入 add() 方法中
结论
- 1)ArrayList 中维护了一个
Object 类型
的数组elementData
- 2)当创建 ArrayList 对象时,如果使用的是无参构造器,则初始 elementData 容器为
0
,第一次添加,则扩容 elementData 为10
,如需再次扩容,则扩容 elementData 为 `1.5 倍 - 3)如果使用的是指定大小的构造器,则初始 elementData 容器为
指定大小
,如果需要扩容,则直接扩容 elementData 为1.5 倍