一、ArrayList概述
ArrayList是Java集合框架中的一个类,它继承了AbstractList类,并且实现了List接口,是一个动态数组,数组长度可以动态增加和缩小,它可以对数组中的元素进行增、删、改、查等操作,还可以存储任意类型的对象。与传统数组相比,ArrayList的容量能动态增长,提供了更便捷的操作接口和灵活的扩展能力。
二、ArrayList特点
- 动态数组:支持动态扩容,使用者无需关心底层数据结构的变化,封装性好。
- 有序存储:存储的元素可重复,并支持null元素的存储,且维护插入顺序。
- 底层为数组:查找快,时间复杂度为O(1);增删慢,尤其是在列表中间或头部操作时,需要移动大量元素,时间复杂度为O(n)。
- 线程不安全:不支持同步,一般建议在单线程中使用ArrayList,在多线程环境下可选择Vector或者CopyOnWriteArrayList。
三、ArrayList继承体系
ArrayList继承AbstractList抽象父类,实现了List、RandomAccess、Cloneable、Serializable接口。
- List接口:定义了List的一些操作规范。
- RandomAccess接口:是一个空接口,代表可随机访问。实现了此接口的集合使用for循环遍历速度更快。
- Cloneable接口:空接口,实现此接口是为了可以调用clone()方法,ArrayList的clone()方法属于浅克隆。
- Serializable接口:空接口,代表可序列化。
四、重要属性
- transient Object[] elementData:这是ArrayList的底层,是一个object数组。由transient修饰,代表此数组不参与序列化,而是使用另外的序列化方式(使用writeObject方法进行序列化,只序列化有值的位置,其他未赋值的位置不进行序列化,节省空间)。
- private int size:指集合包含的元素数量,注意与elementData.length(集合容量)的区别。
五、构造方法
ArrayList有三个构造方法:
- 无参构造方法:
- JDK 8之前:使用无参构造方法创建ArrayList对象时,数组的默认长度为10。例如在JDK 7中,ArrayList空参的构造器会调用本类当中重载的带一个int整型参数指定容量的构造器,将参数10传进去,对当前底层数组就进行了初始化,底层创建了一个长度为10的Object[]类型数组。
public ArrayList() { this.elementData = new Object[10]; }- JDK 8及之后:使用无参构造方法创建ArrayList对象时,数组的默认长度为0。在第一次添加元素时,才开始按照10进行扩容。
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } - 指定初始容量的构造方法:创建一个具有指定初始容量的空ArrayList。
ArrayList<String> list2 = new ArrayList<>(20);
- 包含指定集合的构造方法:创建一个包含指定集合元素的ArrayList,初始容量为集合大小。
import java.util.Arrays; import java.util.Collection; import java.util.ArrayList; Collection<String> collection = Arrays.asList("元素1", "元素2"); ArrayList<String> list3 = new ArrayList<>(collection);
六、常用方法
1. 添加元素
- add(E e):将指定的元素添加到列表的末尾。
list1.add("元素1");
- add(int index, E element):在指定的位置插入指定的元素。
list1.add(1, "元素2");
- addAll(Collection<? extends E> c):按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的末尾。
Collection<String> collection = Arrays.asList("元素3", "元素4"); list1.addAll(collection);
- addAll(int index, Collection<? extends E> c):从指定的位置开始,将指定collection中的所有元素插入到列表中。
list1.addAll(2, collection);
2. 获取元素
- get(int index):返回列表中指定位置的元素。
String element = list1.get(0);
3. 修改元素
- set(int index, E element):用指定的元素替换列表中指定位置的元素。
list1.set(0, "新元素");
4. 删除元素
- remove(int index):移除列表中指定位置的元素。
list1.remove(1);
- remove(Object o):移除列表中第一个出现的指定元素(如果存在)。
list1.remove("元素2");
- removeAll(Collection<?> c):移除列表中所有包含在指定集合中的元素。
list1.removeAll(collection);
- retainAll(Collection<?> c):仅保留列表中包含在指定集合中的元素。
list1.retainAll(collection);
- clear():移除列表中的所有元素。
list1.clear();
5. 查询元素
- contains(Object o):判断列表中是否包含指定的元素。
boolean exists = list1.contains("元素1");
- indexOf(Object o):返回列表中第一次出现的指定元素的索引,如果未找到则返回 -1。
int index = list1.indexOf("元素1");
- lastIndexOf(Object o):返回列表中最后一次出现的指定元素的索引,如果未找到则返回 -1。
int lastIndex = list1.lastIndexOf("元素1");
- isEmpty():判断列表是否为空。
boolean empty = list1.isEmpty();
- size():返回列表中元素的数量。
int size = list1.size();
6. 其他常用方法
- subList(int fromIndex, int toIndex):返回列表中从fromIndex到toIndex(不包括toIndex)的子列表。
import java.util.List; List<String> subList = list1.subList(1, 3);
- toArray():返回一个包含列表中所有元素的数组。
Object[] array = list1.toArray();
- toArray(T[] a):返回一个包含列表中所有元素的数组,数组类型为指定类型。
String[] array = list1.toArray(new String[0]);
七、遍历方式
- 使用增强型for循环
for (String element : list1) { System.out.println(element); }
- 使用Iterator
import java.util.Iterator; Iterator<String> iterator = list1.iterator(); while (iterator.hasNext()) { String element = iterator.next(); System.out.println(element); }
- 使用forEach方法(Java 8+)
list1.forEach(element -> System.out.println(element));
八、动态扩容机制
1. 扩容触发条件
当调用add()方法时,若当前数组容量(elementData.length)小于size + 1,则触发扩容。
2. 扩容规则
新容量计算:newCapacity = oldCapacity + (oldCapacity >> 1)(即原容量的1.5倍)。特殊情况处理:若计算后的容量仍小于所需最小容量(minCapacity),则直接使用minCapacity;若扩容过程中出现整数溢出(极端大容量),抛出OutOfMemoryError。
3. 示例
假设初始情况下,ArrayList的底层数组容量为10,并且向其中添加了11个元素。当尝试再次添加元素时,容量不足,ArrayList会自动进行扩容操作。根据ensureCapacityInternal()方法,计算出自动扩容后最小容量为11,进行扩容,根据grow()方法,得到新容量为15(10 + 10 >> 1),并将旧数组的元素复制到新数组中。
九、使用场景
- 频繁读取数据:例如需要根据索引快速访问元素,ArrayList通过索引访问元素的时间复杂度为O(1),适合此类场景。
- 数据量较小或变化不大:避免频繁扩容带来的性能开销。
- 需要节省内存:ArrayList的内存占用比LinkedList更少。
7192

被折叠的 条评论
为什么被折叠?



