ArrayList 是 Java 集合框架中的一个重要类,它实现了 List 接口,并提供了动态数组的功能。ArrayList 的大小可以随着元素的添加或删除而动态变化,无需手动管理内存。
-
什么是集合
存储空间可变的存储模型,允许存储的数据容量动态变化。
-
ArrayList集合的特点
长度可以变化,只能存储引用数据类型。
-
泛型的使用
用于约束集合中存储元素的数据类型
创建ArrayList
语法
//创建一个空的集合对象
ArrayList<Object> list = new ArrayList<>();
- < >只能填写引用数据类型:Integer,Double,String...(不能写int,double)
- 如果没有指定初始容量,默认初始容量为 10。
常用语法
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// 创建一个存储姓名的 ArrayList
ArrayList<String> names = new ArrayList<>();
// 添加元素
names.add("张三");
names.add("李四");
names.add("王五");
// 打印 ArrayList
System.out.println("当前 ArrayList:" + names); // 输出:[张三, 李四, 王五]
// 获取元素
String name = names.get(1); // 获取第二个元素
System.out.println("第二个元素:" + name); // 输出:李四
// 修改元素
names.set(1, "赵六"); // 将第二个元素修改为 "赵六"
System.out.println("修改后的 ArrayList:" + names); // 输出:[张三, 赵六, 王五]
// 删除元素
names.remove("赵六"); // 删除第一个 "赵六"
System.out.println("删除元素后的 ArrayList:" + names); // 输出:[张三, 王五]
// 检查 ArrayList 是否为空
System.out.println("ArrayList 是否为空:" + names.isEmpty()); // 输出:false
// 清空 ArrayList
names.clear();
System.out.println("清空后的 ArrayList:" + names); // 输出:[]
// 添加元素
names.add("张三");
names.add("李四");
// 将 ArrayList 转换为数组
String[] array = names.toArray(new String[0]);
System.out.println("数组的第一个元素:" + array[0]); // 输出:张三
}
}
方法 | 描述 | 示例 |
---|---|---|
add(E e) | 将元素添加到 ArrayList 末尾 | names.add("张三"); |
add(int index, E element) | 在指定索引处添加元素 | names.add(1, "李四"); |
remove(int index) | 删除指定索引处的元素 | names.remove(1); |
remove(Object o) | 删除第一个匹配的元素 | names.remove("张三"); |
get(int index) | 获取指定索引处的元素 | String name = names.get(1); |
set(int index, E element) | 修改指定索引处的元素 | names.set(1, "王五"); |
size() | 返回 ArrayList 的大小(元素数量) | int size = names.size(); |
isEmpty() | 判断 ArrayList 是否为空 | boolean isEmpty = names.isEmpty(); |
contains(Object o) | 判断 ArrayList 是否包含指定元素 | boolean contains = names.contains("张三"); |
clear() | 清空 ArrayList | names.clear(); |
toArray() | 将 ArrayList 转换为数组 | String[] array = names.toArray(new String[0]); |
优缺点
优点
- 动态大小:
ArrayList
可以根据需要动态调整其大小,不需要在创建时指定固定的大小。 - 快速随机访问:提供 O(1) 时间复杂度的随机访问,因为它基于数组实现,可以通过索引直接访问元素。
- 有序集合:保持元素的插入顺序,允许按照添加元素的顺序遍历列表。
- 允许重复:可以存储重复的元素,适用于需要重复元素的场景。
- 丰富的 API:提供了丰富的操作方法,如添加、删除、修改、查询等,使用方便。
缺点
- 插入和删除性能:在中间位置插入和删除元素的时间复杂度是 O(n),因为需要移动元素以保持列表的连续性。
- 内存浪费:由于需要动态扩容,可能会预分配比实际需要更多的内存,从而导致内存浪费。
- 非线程安全:
ArrayList
不是线程安全的,在多线程环境中使用时需要手动同步或使用同步的ArrayList
。 - 扩容开销:当容量不足时,需要扩容,这个过程涉及到创建一个更大的数组并将旧数组的内容复制到新数组中,时间复杂度为 O(n)。
适用场景
- 频繁读取和遍历操作
- 需要保持元素插入顺序
- 允许重复元素
不适用场景
- 频繁的插入和删除操作,特别是在中间位置
- 需要线程安全的环境
扩容机制
当 ArrayList 中的元素数量超过其当前容量时,ArrayList 会自动扩容,以便能够容纳更多的元素。扩容的具体步骤如下:
- 检查容量:在向 ArrayList 添加新元素时,首先会检查当前容量是否足够。如果容量不足,则需要扩容。
- 计算新容量:新容量通常是旧容量的 1.5 倍。具体来说,新的容量计算公式为
newCapacity = oldCapacity + (oldCapacity >> 1)
。例如,如果当前容量是 10,那么新的容量将是10 + (10 >> 1) = 10 + 5 = 15
。 - 创建新数组:根据计算出的新容量,创建一个新的数组。
- 复制旧数据:将旧数组中的数据复制到新数组中。
- 替换旧数组:用新数组替换旧数组,完成扩容。
- 回收旧数组: 旧数组不再被使用,会被 Java 垃圾回收机制自动回收。
源码分析
1.add
首先看一下 add
方法,这是向 ArrayList
添加元素的入口:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 确保容量足够
elementData[size++] = e;
return true;
}
add
方法:当调用add
方法时,首先调用ensureCapacityInternal
方法来确保ArrayList
有足够的容量来容纳新元素。
2.ensureCapacityInternal
ensureCapacityInternal
方法是确保 ArrayList
有足够的容量来容纳新元素的关键:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
ensureCapacityInternal
方法:这个方法首先调用calculateCapacity
方法来计算所需的最小容量。如果当前的elementData
数组是默认空数组(即初始容量),则返回默认容量和所需容量中的较大值。
3.ensureExplicitCapacity
ensureExplicitCapacity
方法是真正执行扩容操作的地方:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
ensureExplicitCapacity
方法:这个方法增加了modCount
(用于快速失败机制),并检查所需的最小容量是否超过当前数组的长度。如果超过,则调用grow
方法进行扩容。
4.grow
grow
方法是扩容的核心:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
grow
方法:这个方法是扩容的核心。它首先计算新的容量,通常是旧容量的 1.5 倍。如果新的容量仍然不足以满足所需的最小容量,则将新的容量设置为所需的最小容量。如果新的容量超过了最大数组大小,则调用hugeCapacity
方法来处理极端情况。最后,使用Arrays.copyOf
方法将旧数组的数据复制到新数组中。