1. ArrayList 的基本结构
ArrayList
内部使用一个 Object
类型的数组 elementData
来存储所有的元素。数组的长度可以动态调整。
2. 初始容量和扩容机制
- 初始容量:当使用无参构造创建一个
ArrayList
实例时会在底层创建一个默认长度为0的数组,可以通过添加参数指定一个初始容量。添加第一个元素时,底层会创建一个新的长度为10的数组。 - 扩容机制:当添加元素导致数组满时,
ArrayList
会创建一个新数组,新数组的容量通常是旧数组容量的 1.5 倍。
3. 关键操作的实现
添加元素 (add()
)
- 如果
ArrayList
的容量不足以容纳新元素,那么会触发扩容操作。 - 新元素被添加到数组的末尾。
- 扩容操作涉及到创建一个新的数组并将旧数组的内容复制到新的数组中。
- 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
例如:初始长度为10的arraylist添加100个数据,那新数组长度为100,
初始长度为10且arraylist中有10个数据,那新数组长度为110。
删除元素 remove()
- 删除操作会涉及将删除位置之后的所有元素向前移动一位,以填补被删除元素留下的空缺。
获取元素 get()
- 这是一个快速的操作,因为它只需要通过索引访问数组即可。
- 时间复杂度为 O(1)。
设置元素 set()
- 类似于获取操作,设置操作只需要更新特定索引处的元素。
- 时间复杂度为 O(1)。
4. 缩容的细节
- 缩容:
ArrayList
默认不会自动进行缩容。这是因为缩容可能会带来额外的性能开销,特别是在频繁添加和删除元素的情况下。一般情况下,当数组中的元素数量远小于数组的实际容量时,才考虑进行手动缩容。
源码:
public void trimToSize() {
modCount++;
//如果 size 小于 elementData.length,则表明存在多余的空间。
if (size < elementData.length) {
//使用 Arrays.copyOf 方法创建一个新数组,其大小等于当前 size。
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
5. 线程安全性
ArrayList
本身不是线程安全的。如果你需要在一个多线程环境中使用ArrayList
,你需要自己负责同步或者使用Collections.synchronizedList
来获得一个线程安全的视图。
什么是线程安全性?https://blog.csdn.net/m0_61160520/article/details/140719110?spm=1001.2014.3001.5502
6. 性能特点
- 读取操作:由于
ArrayList
是基于数组实现的,因此随机访问非常快,时间复杂度为 O(1)。
"随机访问"(Random Access)是指能够直接访问数据结构中的任意元素,而无需从头开始逐个遍历。这种访问方式在基于数组的数据结构中非常常见
- 插入/删除操作:这些操作涉及到元素的移动,因此时间复杂度通常为 O(n)。特别是对于数组中间的插入或删除操作,需要移动大量的元素。
额外:
Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是 Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合
ArrayList
特点:
-
动态数组:
ArrayList
使用动态数组作为底层数据结构,这意味着它的大小不是固定的。- 当数组空间不足时,
ArrayList
会自动扩展容量,通常是将容量增加到原来的 1.5 倍左右。
-
快速随机访问:
- 由于
ArrayList
使用数组存储元素,可以通过索引直接访问元素,因此支持快速随机访问。 - 根据索引访问元素的时间复杂度为 O(1),即常量时间。
- 由于
-
增删操作较慢:
- 插入或删除元素时,可能需要移动数组中的其他元素。
- 对于插入操作,如果插入位置在数组中间,则需要将插入点之后的所有元素向后移动一位。
- 对于删除操作,如果删除位置在数组中间,则需要将删除点之后的所有元素向前移动一位。
- 这些操作的时间复杂度通常为 O(n),其中 n 是数组中元素的数量。
-
线程不安全:
- 默认情况下,
ArrayList
的实例不是线程安全的。 - 如果需要在多线程环境中使用
ArrayList
,可以使用Collections.synchronizedList
方法将其包装成线程安全的形式。
- 默认情况下,
-
实现了
List
接口中定义的所有方法:还提供了 trimToSize()方法来操作内部数组的大小。 -
可存储任何类型的对象:包括
null
值。