揭秘 Java ArrayList:从源码深度剖析其使用原理
一、引言
在 Java 编程的广袤世界里,数据结构是构建强大、高效程序的基石。而 ArrayList
作为 Java 集合框架中的一颗璀璨明星,以其动态数组的特性和便捷的操作方法,成为了众多开发者在处理数据集合时的首选。无论是简单的数据存储,还是复杂的算法实现,ArrayList
都能发挥出重要的作用。
然而,仅仅会使用 ArrayList
的基本方法是远远不够的。要想在实际开发中充分发挥其性能优势,避免潜在的问题,就需要深入了解其内部的实现原理。本文将带领大家深入到 ArrayList
的源码层面,逐行剖析其核心代码,揭开 ArrayList
的神秘面纱,让大家对这个常用的数据结构有一个全面而深入的认识。
二、ArrayList 概述
2.1 什么是 ArrayList
ArrayList
是 Java 集合框架中 List
接口的一个可调整大小的数组实现。它继承自 AbstractList
类,并实现了 List
、RandomAccess
、Cloneable
和 java.io.Serializable
接口。与传统的数组相比,ArrayList
具有动态扩容的能力,不需要在创建时指定固定的大小,可以根据需要自动调整数组的容量,从而更灵活地存储和管理数据。
2.2 常见应用场景
- 数据存储 :当需要存储一组对象,并且这些对象的数量不确定时,
ArrayList
是一个很好的选择。例如,在一个学生管理系统中,需要存储多个学生的信息,就可以使用ArrayList
来存储学生对象。
java
import java.util.ArrayList;
import java.util.List;
// 定义学生类
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public class StudentManagementSystem {
public static void main(String[] args) {
// 创建一个 ArrayList 来存储学生对象
List<Student> studentList = new ArrayList<>();
// 添加学生对象到列表中
studentList.add(new Student("张三", 20));
studentList.add(new Student("李四", 21));
studentList.add(new Student("王五", 22));
// 遍历列表并输出学生信息
for (Student student : studentList) {
System.out.println("姓名: " + student.getName() + ", 年龄: " + student.getAge());
}
}
}
- 数据处理 :在进行数据处理时,经常需要对一组数据进行遍历、排序、查找等操作。
ArrayList
提供了丰富的方法来支持这些操作,使得数据处理变得更加方便。例如,对一组整数进行排序,可以使用Collections.sort()
方法对ArrayList
中的元素进行排序。
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DataProcessingExample {
public static void main(String[] args) {
// 创建一个 ArrayList 来存储整数
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
// 对列表进行排序
Collections.sort(numbers);
// 输出排序后的列表
for (int number : numbers) {
System.out.print(number + " ");
}
}
}
- 动态扩展 :当需要动态地添加或删除元素时,
ArrayList
可以自动调整数组的大小,确保有足够的空间来存储新元素。例如,在一个在线游戏中,需要动态地添加或删除玩家信息,就可以使用ArrayList
来存储玩家对象。
2.3 简单示例代码
以下是一个简单的 ArrayList
使用示例,展示了如何创建 ArrayList
、添加元素、访问元素和删除元素:
java
import java.util.ArrayList;
import java.util.List;
public class ArrayListSimpleExample {
public static void main(String[] args) {
// 创建一个 ArrayList 对象,用于存储字符串元素
List<String> list = new ArrayList<>();
// 向 ArrayList 中添加元素
list.add("apple");
list.add("banana");
list.add("cherry");
// 访问 ArrayList 中的元素
String firstElement = list.get(0);
System.out.println("第一个元素是: " + firstElement);
// 删除 ArrayList 中的元素
list.remove(1);
System.out.println("删除第二个元素后,列表中的元素为: " + list);
}
}
三、ArrayList 的继承体系
3.1 继承关系
ArrayList
的继承关系如下:
plaintext
java.lang.Object
↳ java.util.AbstractCollection<E>
↳ java.util.AbstractList<E>
↳ java.util.ArrayList<E>
从继承关系可以看出,ArrayList
继承了 AbstractList
类,而 AbstractList
又继承了 AbstractCollection
类。这些抽象类提供了一些通用的实现,使得 ArrayList
可以专注于自身的特性实现。
3.2 继承带来的特性
- 通用方法实现 :通过继承
AbstractList
和AbstractCollection
类,ArrayList
获得了许多通用方法的实现,如addAll()
、removeAll()
、contains()
等。这些方法的实现减少了ArrayList
的代码量,提高了开发效率。
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class InheritedMethodsExample {
public static void main(String[] args) {
// 创建一个 ArrayList 对象
List<String> list1 = new ArrayList<>();
list1.add("apple");
list1.add("banana");
// 创建另一个 ArrayList 对象
List<String> list2 = new ArrayList<>();
list2.add("cherry");
list2.add("date");
// 使用继承的 addAll() 方法将 list2 的元素添加到 list1 中
list1.addAll(list2);
System.out.println("添加后的 list1: " + list1);
// 使用继承的 contains() 方法检查 list1 是否包含某个元素
boolean containsCherry = list1.contains("cherry");
System.out.println("list1 是否包含 cherry: " + containsCherry);
}
}
- 接口一致性 :
ArrayList
实现了List
接口,这使得它可以与其他实现了List
接口的类进行互换使用。例如,可以将一个ArrayList
对象传递给一个接受List
接口类型参数的方法,而不需要担心具体的实现类。
java
import java.util.ArrayList;
import java.util.List;
// 定义一个方法,接受 List 接口类型的参数
public class InterfaceConsistencyExample {
public static void printList(List<String> list) {
for (String element : list) {
System.out.println(element);
}
}
public static void main(String[] args) {
// 创建一个 ArrayList 对象
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
// 调用 printList 方法,传递 ArrayList 对象
printList(list);
}
}
- 可扩展性 :继承体系为
ArrayList
提供了可扩展性。开发者可以通过继承ArrayList
类,重写某些方法来实现自定义的功能。
四、ArrayList 的构造函数
4.1 构造函数种类
ArrayList
提供了三个构造函数,以满足不同的初始化需求:
ArrayList()
:创建一个初始容量为 10 的空列表。
java
// 创建一个初始容量为 10 的空 ArrayList
List<String> list = new ArrayList<>();
ArrayList(int initialCapacity)
:创建一个具有指定初始容量的空列表。
java
// 创建一个初始容量为 20 的空 ArrayList
List<String> list = new ArrayList<>(20);
ArrayList(Collection<? extends E> c)
:创建一个包含指定集合元素的列表,其顺序由集合的迭代器返回。
java
import java.util.Arrays;
import java.util.List;
// 创建一个包含数组元素的 ArrayList
List<Integer> array = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> list = new ArrayList<>(array);
4.2 构造函数源码分析
4.2.1 ArrayList()
构造函数
java
/**
* 构造一个初始容量为 10 的空列表。
*/
public ArrayList() {
// 将 elementData 数组初始化为一个空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
在这个构造函数中,将 elementData
数组初始化为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,这是一个空数组。当第一次添加元素时,ArrayList
会自动将数组的容量扩展为 10。
4.2.2 ArrayList(int initialCapacity)
构造函数
java
/**
* 构造一个具有指定初始容量的空列表。
*
* @param initialCapacity 列表的初始容量
* @throws IllegalArgumentException 如果指定的初始容量为负数
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 如果初始容量大于 0,则创建一个指定大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 如果初始容量为 0,则将 elementData 数组初始化为 EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 如果初始容量为负数,则抛出 IllegalArgumentException 异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
在这个构造函数中,根据传入的初始容量参数进行不同的处理。如果初始容量大于 0,则创建一个指定大小的数组;如果初始容量为 0,则将 elementData
数组初始化为 EMPTY_ELEMENTDATA
;如果初始容量为负数,则抛出 IllegalArgumentException
异常。
4.2.3 ArrayList(Collection<? extends E> c)
构造函数
java
/**
* 构造一个包含指定集合元素的列表,其顺序由集合的迭代器返回。
*
* @param c 其元素将被放置在此列表中的集合
* @throws NullPointerException 如果指定的集合为 null
*/
public ArrayList(Collection<? extends E> c) {
// 将集合转换为数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 如果集合不为空
if (elementData.getClass() != Object[].class)
// 如果数组的类型不是 Object[],则将其转换为 Object[]
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果集合为空,则将 elementData 数组初始化为 EMPTY_ELEMENTDATA
this.elementData = EMPTY_ELEMENTDATA;
}
}
在这个构造函数中,首先将传入的集合转换为数组。如果集合不为空,则检查数组的类型是否为 Object[]
,如果不是,则将其转换为 Object[]
。如果集合为空,则将 elementData
数组初始化为 EMPTY_ELEMENTDATA
。
五、ArrayList 的属性
5.1 主要属性介绍
elementData
:用于存储ArrayList
中的元素,是一个Object
类型的数组。
java
// 存储 ArrayList 元素的数组缓冲区
transient Object[] elementData;
size
:表示ArrayList
中实际存储的元素数量。
java
// ArrayList 中实际存储的元素数量
private int size;
DEFAULT_CAPACITY
:默认的初始容量,值为 10。
java
// 默认的初始容量
private static final int DEFAULT_CAPACITY = 10;
EMPTY_ELEMENTDATA
:空数组,用于初始化ArrayList
时指定初始容量为 0 的情况。
java
// 用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
:空数组,用于无参构造函数初始化ArrayList
的情况。
java
// 用于默认大小的空实例的共享空数组实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
5.2 属性的作用和意义
elementData
:是ArrayList
的核心属性,用于实际存储元素。由于它是一个Object
类型的数组,可以存储任意类型的对象。size
:记录了ArrayList
中实际存储的元素数量,通过size
属性可以方便地获取ArrayList
的大小。DEFAULT_CAPACITY
:默认的初始容量,当使用无参构造函数创建ArrayList
时,第一次添加元素会将数组的容量扩展为DEFAULT_CAPACITY
。EMPTY_ELEMENTDATA
和DEFAULTCAPACITY_EMPTY_ELEMENTDATA
:这两个空数组用于不同的初始化场景,避免了不必要的数组创建。
六、ArrayList 的常用方法
6.1 添加元素方法
6.1.1 add(E e)
方法
java
/**
* 将指定的元素添加到此列表的末尾。
*
* @param e 要添加到此列表的元素
* @return true(根据 Collection.add(E) 的规定)
*/
public boolean add(E e) {
// 确保内部容量足够,以容纳新元素
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将新元素添加到数组的末尾
elementData[size++] = e;
return true;
}
在这个方法中,首先调用 ensureCapacityInternal()
方法确保数组有足够的容量来存储新元素。然后,将新元素添加到数组的末尾,并将 size
加 1。
6.1.2 add(int index, E element)
方法
java
/**
* 将指定的元素插入此列表中的指定位置。
* 将当前位于该位置的元素(如果有)和任何后续元素向右移动(在其索引中添加一个)。
*
* @param index 要插入指定元素的索引
* @param element 要插入的元素
* @throws IndexOutOfBoundsException 如果索引超出范围 (index < 0 || index > size())
*/
public void add(int index, E element) {
// 检查索引是否越界
rangeCheckForAdd(index);
// 确保内部容量足够,以容纳新元素
ensureCapacityInternal(size + 1); // Increments modCount!!
// 将指定位置及其后面的元素向后移动一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 将新元素插入到指定位置
elementData[index] = element;
// 增加元素数量
size++;
}
在这个方法中,首先调用 rangeCheckForAdd()
方法检查索引是否越界。然后,调用 ensureCapacityInternal()
方法确保数组有足够的容量来存储新元素。接着,使用 System.arraycopy()
方法将指定位置及其后面的元素向后移动一位。最后,将新元素插入到指定位置,并将 size
加 1。
6.2 删除元素方法
6.2.1 remove(int index)
方法
java
/**
* 移除该列表中指定位置的元素。
* 将任何后续元素向左移动(从其索引中减去一个)。
*
* @param index 要移除的元素的索引
* @return 从列表中移除的元素
* @throws IndexOutOfBoundsException 如果索引超出范围 (index < 0 || index >= size())
*/
public E remove(int index) {
// 检查索引是否越界
rangeCheck(index);
// 记录修改次数
modCount++;
// 获取要移除的元素
E oldValue = elementData(index);
// 计算需要移动的元素数量
int numMoved = size - index - 1;
if (numMoved > 0)
// 将指定位置后面的元素向前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将数组末尾的元素置为 null,以便垃圾回收
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
在这个方法中,首先调用 rangeCheck()
方法检查索引是否越界。然后,记录修改次数,获取要移除的元素。接着,使用 System.arraycopy()
方法将指定位置后面的元素向前移动一位。最后,将数组末尾的元素置为 null
,以便垃圾回收,并返回被移除的元素。
6.2.2 remove(Object o)
方法
java
/**
* 从该列表中移除指定元素的第一个匹配项(如果存在)。
* 如果列表不包含该元素,则它保持不变。
* 更正式地,移除具有最低索引 i 的元素,使得 (o==null ? get(i)==null : o.equals(get(i)))
* (如果存在这样的元素)。
* 如果列表包含指定的元素,则返回 true(或者等价地,如果列表由于调用而更改)。
*
* @param o 要从此列表中移除的元素(如果存在)
* @return 如果此列表包含指定的元素,则返回 true
*/
public boolean remove(Object o) {
if (o == null) {
// 遍历数组,查找第一个为 null 的元素
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 移除该元素
fastRemove(index);
return true;
}
} else {
// 遍历数组,查找第一个与指定对象相等的元素
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
// 移除该元素
fastRemove(index);
return true;
}
}
return false;
}
/*
* 私有的移除方法,跳过边界检查并且不返回被移除的元素。
*/
private void fastRemove(int index) {
// 记录修改次数
modCount++;
// 计算需要移动的元素数量
int numMoved = size - index - 1;
if (numMoved > 0)
// 将指定位置后面的元素向前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将数组末尾的元素置为 null,以便垃圾回收
elementData[--size] = null; // clear to let GC do its work
}
在这个方法中,首先判断要移除的元素是否为 null
。如果为 null
,则遍历数组,查找第一个为 null
的元素,并调用 fastRemove()
方法移除该元素。如果不为 null
,则遍历数组,查找第一个与指定对象相等的元素,并调用 fastRemove()
方法移除该元素。如果找到了匹配的元素,则返回 true
,否则返回 false
。
6.3 获取元素方法
6.3.1 get(int index)
方法
java
/**
* 返回此列表中指定位置的元素。
*
* @param index 要返回的元素的索引
* @return 此列表中指定位置的元素
* @throws IndexOutOfBoundsException 如果索引超出范围 (index < 0 || index >= size())
*/
public E get(int index) {
// 检查索引是否越界
rangeCheck(index);
// 返回指定位置的元素
return elementData(index);
}
@SuppressWarnings("unchecked")
E elementData(int index) {
// 获取指定位置的元素
return (E) elementData[index];
6.3.2 源码详细解读
在 get(int index)
方法中,首先调用了 rangeCheck(index)
方法来检查传入的索引是否越界。这是一个非常重要的步骤,因为如果索引超出了 ArrayList
当前的有效范围,会导致程序出现异常,影响程序的稳定性。以下是 rangeCheck
方法的源码:
java
/**
* 检查给定的索引是否在范围内。
* 如果不在范围内,抛出 IndexOutOfBoundsException 异常。
*
* @param index 要检查的索引
*/
private void rangeCheck(int index) {
if (index >= size)
// 若索引大于等于当前列表的大小,说明越界,抛出异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 构建一个越界异常的详细信息。
*
* @param index 越界的索引
* @return 包含越界信息的字符串
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
rangeCheck
方法会比较传入的索引和当前 ArrayList
的 size
属性。如果索引大于等于 size
,就意味着该索引超出了列表的有效范围,此时会调用 outOfBoundsMsg
方法构建一个详细的越界异常信息,并抛出 IndexOutOfBoundsException
异常。
当索引检查通过后,get(int index)
方法会调用 elementData(int index)
方法来获取指定位置的元素。elementData
方法很简单,它直接从 elementData
数组中取出对应索引位置的元素,并将其强制转换为泛型类型 E
返回。
6.3.3 性能分析
get(int index)
方法的时间复杂度是 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),这是因为 ArrayList
是基于数组实现的,数组具有随机访问的特性。通过索引可以直接定位到数组中的元素,不需要遍历整个数组。因此,在需要频繁随机访问元素的场景下,ArrayList
是一个很好的选择。
6.3.4 示例代码
以下是一个简单的示例,展示了如何使用 get
方法获取 ArrayList
中的元素:
java
import java.util.ArrayList;
import java.util.List;
public class GetElementExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
// 使用 get 方法获取指定位置的元素
String secondFruit = fruits.get(1);
System.out.println("第二个水果是: " + secondFruit);
}
}
6.4 修改元素方法
6.4.1 set(int index, E element)
方法
java
/**
* 用指定的元素替换此列表中指定位置的元素。
*
* @param index 要替换的元素的索引
* @param element 要存储在指定位置的元素
* @return 以前在指定位置的元素
* @throws IndexOutOfBoundsException 如果索引超出范围 (index < 0 || index >= size())
*/
public E set(int index, E element) {
// 检查索引是否越界
rangeCheck(index);
// 获取指定位置的旧元素
E oldValue = elementData(index);
// 将新元素设置到指定位置
elementData[index] = element;
return oldValue;
}
6.4.2 源码详细解读
set(int index, E element)
方法的主要功能是将指定位置的元素替换为新的元素,并返回原来的元素。该方法同样首先调用 rangeCheck
方法检查索引是否越界,确保操作的安全性。
在索引检查通过后,使用 elementData(index)
方法获取指定位置的旧元素,然后将新元素赋值给 elementData
数组的对应位置。最后返回旧元素,方便调用者获取被替换掉的元素信息。
6.4.3 性能分析
set
方法的时间复杂度也是 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为它只涉及到数组的随机访问和赋值操作,不需要遍历数组。这使得在需要修改特定位置元素的场景下,ArrayList
能够高效地完成任务。
6.4.4 示例代码
以下是一个使用 set
方法修改 ArrayList
元素的示例:
java
import java.util.ArrayList;
import java.util.List;
public class SetElementExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> colors = new ArrayList<>();
colors.add("Red");
colors.add("Green");
colors.add("Blue");
// 使用 set 方法修改第二个元素
String oldColor = colors.set(1, "Yellow");
System.out.println("被替换的颜色是: " + oldColor);
System.out.println("修改后的列表: " + colors);
}
}
6.5 其他常用方法
6.5.1 size()
方法
java
/**
* 返回此列表中的元素数量。
*
* @return 此列表中的元素数量
*/
public int size() {
// 返回 size 属性的值
return size;
}
源码解读
size()
方法非常简单,它直接返回 ArrayList
的 size
属性值。size
属性记录了当前 ArrayList
中实际存储的元素数量,通过这个方法可以方便地获取列表的大小。
性能分析
size()
方法的时间复杂度是 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为它只是简单地返回一个属性值,不需要进行任何遍历或计算操作。
示例代码
java
import java.util.ArrayList;
import java.util.List;
public class SizeMethodExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
// 使用 size 方法获取列表的大小
int listSize = numbers.size();
System.out.println("列表的大小是: " + listSize);
}
}
6.5.2 isEmpty()
方法
java
/**
* 如果此列表不包含任何元素,则返回 true。
*
* @return 如果此列表不包含任何元素,则返回 true
*/
public boolean isEmpty() {
// 判断 size 属性的值是否为 0
return size == 0;
}
源码解读
isEmpty()
方法通过比较 size
属性和 0 的值来判断 ArrayList
是否为空。如果 size
为 0,说明列表中没有元素,返回 true
;否则返回 false
。
性能分析
isEmpty()
方法的时间复杂度同样是 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为它只涉及到一个简单的比较操作。
示例代码
java
import java.util.ArrayList;
import java.util.List;
public class IsEmptyMethodExample {
public static void main(String[] args) {
// 创建一个空的 ArrayList
List<String> emptyList = new ArrayList<>();
// 创建一个包含元素的 ArrayList
List<String> nonEmptyList = new ArrayList<>();
nonEmptyList.add("Item");
// 使用 isEmpty 方法检查列表是否为空
System.out.println("空列表是否为空: " + emptyList.isEmpty());
System.out.println("非空列表是否为空: " + nonEmptyList.isEmpty());
}
}
6.5.3 contains(Object o)
方法
java
/**
* 如果此列表包含指定的元素,则返回 true。
* 更正式地,返回 true 当且仅当此列表包含至少一个元素 e,使得 (o==null ? e==null : o.equals(e))。
*
* @param o 要测试此列表中是否存在的元素
* @return 如果此列表包含指定的元素,则返回 true
*/
public boolean contains(Object o) {
// 调用 indexOf() 方法查找元素的索引
return indexOf(o) >= 0;
}
/**
* 返回此列表中指定元素的第一个匹配项的索引,如果此列表不包含该元素,则返回 -1。
* 更正式地,返回最低索引 i,使得 (o==null ? get(i)==null : o.equals(get(i))),
* 或者如果没有这样的索引,则返回 -1。
*
* @param o 要搜索的元素
* @return 此列表中指定元素的第一个匹配项的索引,如果此列表不包含该元素,则返回 -1
*/
public int indexOf(Object o) {
if (o == null) {
// 遍历数组,查找第一个为 null 的元素
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
// 遍历数组,查找第一个与指定对象相等的元素
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
源码解读
contains(Object o)
方法通过调用 indexOf(o)
方法来判断列表中是否包含指定的元素。indexOf
方法会遍历 elementData
数组,查找与指定元素相等的元素。如果元素为 null
,则查找第一个为 null
的元素;否则使用 equals
方法进行比较。如果找到了匹配的元素,返回其索引;如果没有找到,返回 -1。
contains
方法根据 indexOf
方法的返回值来判断列表中是否包含指定元素,如果返回值大于等于 0,说明找到了元素,返回 true
;否则返回 false
。
性能分析
contains
方法和 indexOf
方法的时间复杂度都是 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),因为在最坏情况下,需要遍历整个数组才能确定元素是否存在。
示例代码
java
import java.util.ArrayList;
import java.util.List;
public class ContainsMethodExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> animals = new ArrayList<>();
animals.add("Dog");
animals.add("Cat");
animals.add("Bird");
// 使用 contains 方法检查列表中是否包含某个元素
boolean containsCat = animals.contains("Cat");
boolean containsFish = animals.contains("Fish");
System.out.println("列表中是否包含 Cat: " + containsCat);
System.out.println("列表中是否包含 Fish: " + containsFish);
}
}
6.6 批量操作方法
6.6.1 addAll(Collection<? extends E> c)
方法
java
/**
* 将指定集合中的所有元素按指定集合的迭代器返回的顺序追加到此列表的末尾。
* 如果在操作进行过程中修改了指定的集合,则此操作的行为是未定义的。
* (这意味着如果指定的集合是此列表,并且此列表是非空的,则此调用的行为是未定义的。)
*
* @param c 包含要添加到此列表的元素的集合
* @return 如果此列表因调用而更改,则返回 true
*/
public boolean addAll(Collection<? extends E> c) {
// 将集合转换为数组
Object[] a = c.toArray();
int numNew = a.length;
// 确保内部容量足够,以容纳新元素
ensureCapacityInternal(size + numNew); // Increments modCount
// 将新元素复制到列表的末尾
System.arraycopy(a, 0, elementData, size, numNew);
// 更新列表的大小
size += numNew;
return numNew != 0;
}
源码解读
addAll(Collection<? extends E> c)
方法的作用是将指定集合中的所有元素添加到当前 ArrayList
的末尾。首先,将传入的集合转换为数组 a
,并获取数组的长度 numNew
,表示要添加的元素数量。
然后,调用 ensureCapacityInternal
方法确保 ArrayList
的内部数组有足够的容量来容纳新元素。接着,使用 System.arraycopy
方法将新元素复制到 elementData
数组的末尾。最后,更新 size
属性的值,并根据添加的元素数量是否为 0 来返回 true
或 false
。
性能分析
该方法的时间复杂度主要取决于要添加的元素数量和可能的扩容操作。如果不需要扩容,时间复杂度是 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( k ) O(k) </math>O(k),其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> k k </math>k 是要添加的元素数量。如果需要扩容,时间复杂度会受到扩容操作的影响。
示例代码
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class AddAllMethodExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> list1 = new ArrayList<>();
list1.add("Apple");
list1.add("Banana");
// 创建另一个集合
List<String> list2 = Arrays.asList("Cherry", "Date");
// 使用 addAll 方法将 list2 的元素添加到 list1 中
boolean changed = list1.addAll(list2);
System.out.println("列表是否更改: " + changed);
System.out.println("添加后的列表: " + list1);
}
}
6.6.2 addAll(int index, Collection<? extends E> c)
方法
java
/**
* 将指定集合中的所有元素插入到此列表中的指定位置。
* 将当前位于该位置的元素(如果有)和任何后续元素向右移动(增加其索引)。
* 新元素将按指定集合的迭代器返回的顺序出现在列表中。
*
* @param index 要插入指定集合中第一个元素的索引
* @param c 包含要添加到此列表的元素的集合
* @return 如果此列表因调用而更改,则返回 true
* @throws IndexOutOfBoundsException 如果索引超出范围 (index < 0 || index > size())
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 检查索引是否越界
rangeCheckForAdd(index);
// 将集合转换为数组
Object[] a = c.toArray();
int numNew = a.length;
// 确保内部容量足够,以容纳新元素
ensureCapacityInternal(size + numNew); // Increments modCount
// 计算需要移动的元素数量
int numMoved = size - index;
if (numMoved > 0)
// 将指定位置及其后面的元素向后移动 numNew 个位置
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
// 将新元素复制到指定位置
System.arraycopy(a, 0, elementData, index, numNew);
// 更新列表的大小
size += numNew;
return numNew != 0;
}
源码解读
addAll(int index, Collection<? extends E> c)
方法用于将指定集合中的元素插入到 ArrayList
的指定位置。首先,调用 rangeCheckForAdd
方法检查索引是否越界。
接着,将传入的集合转换为数组 a
,并获取要添加的元素数量 numNew
。然后,调用 ensureCapacityInternal
方法确保数组有足够的容量。
如果指定位置后面有元素(numMoved > 0
),使用 System.arraycopy
方法将这些元素向后移动 numNew
个位置。最后,将新元素复制到指定位置,并更新 size
属性。
性能分析
该方法的时间复杂度同样受到要添加的元素数量和可能的扩容操作影响。此外,还需要移动部分元素,因此在最坏情况下,时间复杂度可能达到 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n + k ) O(n + k) </math>O(n+k),其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 是移动的元素数量, <math xmlns="http://www.w3.org/1998/Math/MathML"> k k </math>k 是要添加的元素数量。
示例代码
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class AddAllAtIndexMethodExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> list1 = new ArrayList<>();
list1.add("Apple");
list1.add("Banana");
// 创建另一个集合
List<String> list2 = Arrays.asList("Cherry", "Date");
// 使用 addAll 方法将 list2 的元素插入到 list1 的指定位置
boolean changed = list1.addAll(1, list2);
System.out.println("列表是否更改: " + changed);
System.out.println("插入后的列表: " + list1);
}
}
6.6.3 removeAll(Collection<?> c)
方法
java
/**
* 从此列表中移除指定集合中包含的所有元素。
*
* @param c 包含要从此列表中移除的元素的集合
* @return 如果此列表因调用而更改,则返回 true
*/
public boolean removeAll(Collection<?> c) {
// 检查传入的集合是否为 null
Objects.requireNonNull(c);
return batchRemove(c, false);
}
/**
* 批量移除元素的私有方法。
*
* @param c 包含要移除或保留的元素的集合
* @param complement 如果为 true,则保留 c 中的元素;如果为 false,则移除 c 中的元素
* @return 如果列表因调用而更改,则返回 true
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
// 如果元素不需要移除,则将其保留
elementData[w++] = elementData[r];
} finally {
// 确保在发生异常时也能正确处理
if (r != size) {
// 将未处理的元素复制到保留的元素后面
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// 将不需要保留的元素置为 null,以便垃圾回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
源码解读
removeAll(Collection<?> c)
方法用于移除当前 ArrayList
中所有存在于指定集合 c
中的元素。它调用了 batchRemove
方法来完成实际的移除操作。
batchRemove
方法使用两个指针 r
和 w
来遍历数组。r
指针用于遍历原数组中的元素,w
指针用于记录保留元素的位置。如果元素不需要移除(根据 complement
参数判断),则将其复制到 w
指针指向的位置,并将 w
指针向后移动一位。
在 finally
块中,处理可能出现的异常情况,确保即使在发生异常时也能正确处理数组。最后,将不需要保留的元素置为 null
,更新 size
属性和 modCount
属性,并返回列表是否发生了更改。
性能分析
该方法的时间复杂度是 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ∗ m ) O(n * m) </math>O(n∗m),其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 是当前 ArrayList
的元素数量, <math xmlns="http://www.w3.org/1998/Math/MathML"> m m </math>m 是指定集合 c
的元素数量。因为在遍历 ArrayList
时,每次都需要调用 c.contains
方法来判断元素是否需要移除。
示例代码
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class RemoveAllMethodExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> list1 = new ArrayList<>();
list1.add("Apple");
list1.add("Banana");
list1.add("Cherry");
// 创建另一个集合
List<String> list2 = Arrays.asList("Banana", "Date");
// 使用 removeAll 方法移除 list1 中存在于 list2 的元素
boolean changed = list1.removeAll(list2);
System.out.println("列表是否更改: " + changed);
System.out.println("移除后的列表: " + list1);
}
}
6.6.4 retainAll(Collection<?> c)
方法
java
/**
* 仅保留此列表中包含在指定集合中的元素。
* 换句话说,从此列表中移除未包含在指定集合中的所有元素。
*
* @param c 包含要保留在此列表中的元素的集合
* @return 如果此列表因调用而更改,则返回 true
*/
public boolean retainAll(Collection<?> c) {
// 检查传入的集合是否为 null
Objects.requireNonNull(c);
return batchRemove(c, true);
}
源码解读
retainAll(Collection<?> c)
方法用于仅保留当前 ArrayList
中存在于指定集合 c
中的元素,移除其他元素。它同样调用了 batchRemove
方法,只是将 complement
参数设置为 true
,表示保留 c
中的元素。
性能分析
与 removeAll
方法类似,retainAll
方法的时间复杂度也是 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ∗ m ) O(n * m) </math>O(n∗m),其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 是当前 ArrayList
的元素数量, <math xmlns="http://www.w3.org/1998/Math/MathML"> m m </math>m 是指定集合 c
的元素数量。
示例代码
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class RetainAllMethodExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> list1 = new ArrayList<>();
list1.add("Apple");
list1.add("Banana");
list1.add("Cherry");
// 创建另一个集合
List<String> list2 = Arrays.asList("Banana", "Date");
// 使用 retainAll 方法仅保留 list1 中存在于 list2 的元素
boolean changed = list1.retainAll(list2);
System.out.println("列表是否更改: " + changed);
System.out.println("保留后的列表: " + list1);
}
}
6.6.5 clear()
方法
java
/**
* 从此列表中移除所有元素。
* 此调用返回后,列表将为空。
*/
public void clear() {
modCount++;
// 遍历数组,将每个元素置为 null,以便垃圾回收
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
源码解读
clear()
方法用于清空 ArrayList
中的所有元素。首先,它会增加 modCount
的值,modCount
是 ArrayList
用于记录列表结构修改次数的变量,在迭代器操作时会用于检测并发修改异常。
接着,通过一个 for
循环将 elementData
数组中的每个元素都置为 null
。这样做的目的是让这些元素不再被引用,从而可以被垃圾回收机制回收,释放内存。
最后,将 size
属性设置为 0,表示列表中不再有元素。
性能分析
该方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 是列表中当前元素的数量,因为需要遍历数组将每个元素置为 null
。
示例代码
java
import java.util.ArrayList;
import java.util.List;
public class ClearMethodExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
System.out.println("清空之前的列表: " + fruits);
// 调用 clear 方法清空列表
fruits.clear();
System.out.println("清空之后的列表: " + fruits);
}
}
6.7 数组转换方法
6.7.1 toArray()
方法
java
/**
* 以正确的顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组。
* 返回的数组将是"安全的",因为此列表不保留对它的引用。
* (换句话说,此方法必须分配一个新数组)。
* 因此,调用者可以自由地修改返回的数组。
* 此方法充当基于数组和基于集合的 API 之间的桥梁。
*
* @return 包含此列表中所有元素的数组
*/
public Object[] toArray() {
// 使用 Arrays.copyOf 方法复制 elementData 数组
return Arrays.copyOf(elementData, size);
}
源码解读
toArray()
方法将 ArrayList
中的所有元素转换为一个 Object
类型的数组并返回。它调用了 Arrays.copyOf
方法,该方法会创建一个新的数组,并将 elementData
数组中从索引 0 到 size - 1
的元素复制到新数组中。这样做的好处是返回的数组和原 ArrayList
是相互独立的,对返回数组的修改不会影响原 ArrayList
,反之亦然。
性能分析
该方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),因为需要复制数组中的所有元素,其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 是列表中元素的数量。
示例代码
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ToArrayMethodExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> colors = new ArrayList<>();
colors.add("Red");
colors.add("Green");
colors.add("Blue");
// 调用 toArray 方法将列表转换为数组
Object[] colorArray = colors.toArray();
System.out.println("转换后的数组: " + Arrays.toString(colorArray));
}
}
6.7.2 toArray(T[] a)
方法
java
/**
* 以正确的顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组;
* 返回数组的运行时类型是指定数组的运行时类型。
* 如果列表适合指定的数组,则在其中返回。
* 否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。
*
* 如果列表适合指定的数组并有剩余空间(即,数组的元素比列表多),
* 则紧跟在列表末尾的数组元素被设置为 null。
* (这仅在调用者知道列表不包含任何 null 元素时才有助于确定列表的长度。)
*
* @param <T> 包含列表的数组的组件类型
* @param a 要存储列表元素的数组,如果它足够大;
* 否则,将为此目的分配相同运行时类型的新数组
* @return 包含列表元素的数组
* @throws ArrayStoreException 如果指定数组的运行时类型不是此列表中每个元素的运行时类型的超类型
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// 如果指定数组的长度小于列表的大小,创建一个新的数组
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
// 将列表元素复制到指定数组中
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
// 如果指定数组的长度大于列表的大小,将多余的元素置为 null
a[size] = null;
return a;
}
源码解读
toArray(T[] a)
方法允许将 ArrayList
中的元素存储到指定类型的数组中。首先,它会检查传入数组 a
的长度是否小于列表的大小 size
。如果是,则使用 Arrays.copyOf
方法创建一个新的数组,该数组的长度为 size
,类型与传入数组 a
相同,并将 elementData
数组中的元素复制到新数组中。
如果传入数组 a
的长度大于等于列表的大小 size
,则使用 System.arraycopy
方法将 elementData
数组中的元素复制到 a
数组中。如果 a
数组的长度大于 size
,则将 a
数组中索引为 size
的元素置为 null
。
最后,返回存储了列表元素的数组。
性能分析
该方法的时间复杂度同样为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),因为需要复制数组中的所有元素,其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 是列表中元素的数量。
示例代码
java
import java.util.ArrayList;
import java.util.List;
public class ToArrayWithTypeExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
// 创建一个 String 类型的数组
String[] fruitArray = new String[fruits.size()];
// 调用 toArray 方法将列表元素存储到数组中
fruitArray = fruits.toArray(fruitArray);
for (String fruit : fruitArray) {
System.out.println(fruit);
}
}
}
6.8 迭代器相关方法
6.8.1 iterator()
方法
java
/**
* 返回此列表中元素的迭代器,按适当顺序。
*
* @return 此列表中元素的迭代器,按适当顺序
*/
public Iterator<E> iterator() {
// 创建一个 Itr 类型的迭代器对象
return new Itr();
}
/**
* 迭代器实现类
*/
private class Itr implements Iterator<E> {
int cursor; // 下一个要返回的元素的索引
int lastRet = -1; // 最后一个返回的元素的索引;如果没有,则为 -1
int expectedModCount = modCount;
/**
* 如果迭代器还有更多元素,则返回 true。
*
* @return 如果迭代器还有更多元素,则返回 true
*/
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
/**
* 返回迭代器中的下一个元素。
*
* @return 迭代器中的下一个元素
* @throws NoSuchElementException 如果迭代器没有更多元素
*/
public E next() {
// 检查是否有并发修改
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
/**
* 从底层集合中移除迭代器返回的最后一个元素。
* 每次调用 next 只能调用一次此方法。
* 如果在迭代过程中以除调用此方法之外的任何方式修改了底层集合,
* 则迭代器的行为是未定义的。
*
* @throws IllegalStateException 如果 next 方法尚未调用,
* 或者在最后一次调用 next 方法之后已经调用了 remove 方法
*/
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
// 检查是否有并发修改
checkForComodification();
try {
// 调用 ArrayList 的 remove 方法移除元素
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
// 确保消费者不为 null
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// 更新游标和检查并发修改
cursor = i;
lastRet = i - 1;
checkForComodification();
}
/**
* 检查是否有并发修改。
* 如果 modCount 不等于 expectedModCount,则抛出 ConcurrentModificationException 异常。
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
源码解读
iterator()
方法返回一个 Itr
类型的迭代器对象,用于遍历 ArrayList
中的元素。Itr
类是 ArrayList
的一个内部类,实现了 Iterator
接口。
hasNext()
方法通过比较 cursor
(下一个要返回的元素的索引)和 size
(列表的大小)来判断迭代器是否还有更多元素。
next()
方法用于返回迭代器中的下一个元素。在返回元素之前,会调用 checkForComodification()
方法检查是否有并发修改。如果有并发修改(即 modCount
不等于 expectedModCount
),会抛出 ConcurrentModificationException
异常。
remove()
方法用于移除迭代器返回的最后一个元素。在移除元素之前,同样会检查是否有并发修改。移除元素后,会更新 cursor
和 lastRet
的值,并更新 expectedModCount
。
forEachRemaining()
方法用于对迭代器中剩余的元素执行指定的操作。它会遍历剩余的元素,并将每个元素传递给 Consumer
进行处理。
checkForComodification()
方法是一个辅助方法,用于检查是否有并发修改。如果 modCount
和 expectedModCount
不相等,说明在迭代过程中列表的结构被修改了,会抛出 ConcurrentModificationException
异常。
性能分析
iterator()
方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为只是创建一个迭代器对象。hasNext()
方法的时间复杂度也为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为只进行简单的比较操作。next()
方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为只是访问数组中的元素。remove()
方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),因为在移除元素时可能需要移动后面的元素。
示例代码
java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> animals = new ArrayList<>();
animals.add("Dog");
animals.add("Cat");
animals.add("Bird");
// 获取迭代器
Iterator<String> iterator = animals.iterator();
// 使用迭代器遍历列表
while (iterator.hasNext()) {
String animal = iterator.next();
System.out.println(animal);
}
}
}
6.8.2 listIterator()
方法
java
/**
* 返回此列表中元素的列表迭代器,按适当顺序。
*
* @return 此列表中元素的列表迭代器,按适当顺序
*/
public ListIterator<E> listIterator() {
// 创建一个 ListItr 类型的列表迭代器对象,从索引 0 开始
return new ListItr(0);
}
/**
* 返回此列表中元素的列表迭代器,从列表中的指定位置开始。
* 指定的索引表示 next 方法的初始调用将返回的第一个元素。
* previous 方法的初始调用将返回具有指定索引减 1 的元素。
*
* @param index 从列表迭代器返回的第一个元素的索引(通过调用 next 方法)
* @return 此列表中元素的列表迭代器,从列表中的指定位置开始
* @throws IndexOutOfBoundsException 如果索引超出范围 (index < 0 || index > size())
*/
public ListIterator<E> listIterator(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: "+index);
// 创建一个 ListItr 类型的列表迭代器对象,从指定索引开始
return new ListItr(index);
}
/**
* 列表迭代器实现类
*/
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
/**
* 如果此列表迭代器在向前遍历列表时有更多元素,则返回 true。
*
* @return 如果此列表迭代器在向前遍历列表时有更多元素,则返回 true
*/
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
/**
* 返回列表中的下一个元素,并将光标位置向前移动。
*
* @return 列表中的下一个元素
* @throws NoSuchElementException 如果迭代器没有更多元素
*/
public E next() {
// 检查是否有并发修改
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
/**
* 如果此列表迭代器在反向遍历列表时有更多元素,则返回 true。
*
* @return 如果此列表迭代器在反向遍历列表时有更多元素,则返回 true
*/
public boolean hasPrevious() {
return cursor != 0;
}
@SuppressWarnings("unchecked")
/**
* 返回列表中的上一个元素,并将光标位置向后移动。
*
* @return 列表中的上一个元素
* @throws NoSuchElementException 如果迭代器没有更多元素
*/
public E previous() {
// 检查是否有并发修改
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
/**
* 返回对 next 方法的后续调用将返回的元素的索引。
* 如果列表迭代器位于列表末尾,则返回列表的大小。
*
* @return 对 next 方法的后续调用将返回的元素的索引,
* 或者如果列表迭代器位于列表末尾,则返回列表的大小
*/
public int nextIndex() {
return cursor;
}
/**
* 返回对 previous 方法的后续调用将返回的元素的索引。
* 如果列表迭代器位于列表开头,则返回 -1。
*
* @return 对 previous 方法的后续调用将返回的元素的索引,
* 或者如果列表迭代器位于列表开头,则返回 -1
*/
public int previousIndex() {
return cursor - 1;
}
/**
* 用指定的元素替换 next 或 previous 方法返回的最后一个元素。
* 只有在最后一次调用 next 或 previous 之后没有调用 remove 或 add 方法时,才能进行此调用。
*
* @param e 要替换 next 或 previous 方法返回的最后一个元素的元素
* @throws IllegalStateException 如果 lastRet 为 -1
* (即,既没有调用 next 也没有调用 previous,
* 或者在最后一次调用 next 或 previous 之后调用了 remove 或 add 方法)
* @throws ConcurrentModificationException 如果在迭代过程中以除调用此方法之外的任何方式修改了底层集合
*/
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
// 检查是否有并发修改
checkForComodification();
try {
// 调用 ArrayList 的 set 方法替换元素
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
/**
* 将指定的元素插入列表,紧接在 next 方法返回的元素之前(如果有),
* 或者紧接在 previous 方法返回的元素之后(如果有)。
* 新元素被插入到隐式光标之前;
* 对 next 的下一次调用不受影响,而对 previous 的下一次调用将返回新元素。
* (此调用将调用 nextIndex 或 previousIndex 返回的值增加 1。)
*
* @param e 要插入的元素
*/
public void add(E e) {
// 检查是否有并发修改
checkForComodification();
try {
int i = cursor;
// 调用 ArrayList 的 add 方法插入元素
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
源码解读
listIterator()
方法返回一个 ListItr
类型的列表迭代器对象,用于遍历 ArrayList
中的元素。ListItr
类继承自 Itr
类,并实现了 ListIterator
接口,提供了比普通迭代器更丰富的功能,如双向遍历、插入和替换元素等。
listIterator(int index)
方法允许从指定的索引位置开始迭代。
hasNext()
和 next()
方法的实现与 Itr
类中的实现类似,用于向前遍历列表。
hasPrevious()
方法通过比较 cursor
和 0 来判断是否可以反向遍历。previous()
方法用于返回上一个元素,并将光标位置向后移动。
nextIndex()
方法返回下一个元素的索引,previousIndex()
方法返回上一个元素的索引。
set(E e)
方法用于替换 next
或 previous
方法返回的最后一个元素。在替换元素之前,会检查是否有并发修改,并确保 lastRet
不为 -1。
add(E e)
方法用于在当前光标位置插入一个新元素。插入元素后,会更新 cursor
和 lastRet
的值,并更新 expectedModCount
。
性能分析
listIterator()
方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为只是创建一个列表迭代器对象。hasNext()
、hasPrevious()
、nextIndex()
和 previousIndex()
方法的时间复杂度都为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为只进行简单的比较或计算操作。next()
和 previous()
方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为只是访问数组中的元素。set(E e)
方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为只是替换数组中的元素。add(E e)
方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),因为在插入元素时可能需要移动后面的元素。
示例代码
java
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> colors = new ArrayList<>();
colors.add("Red");
colors.add("Green");
colors.add("Blue");
// 获取列表迭代器,从索引 1 开始
ListIterator<String> listIterator = colors.listIterator(1);
// 向前遍历
while (listIterator.hasNext()) {
String color = listIterator.next();
System.out.println("向前遍历: " + color);
}
// 反向遍历
while (listIterator.hasPrevious()) {
String color = listIterator.previous();
System.out.println("反向遍历: " + color);
}
}
}
6.9 排序方法
6.9.1 sort(Comparator<? super E> c)
方法
java
/**
* 根据指定的比较器对这个列表进行排序。
*
* 所有元素都必须使用指定的比较器相互比较
* (即,c.compare(e1, e2) 不得为列表中的任何元素 e1 和 e2 抛出 ClassCastException)。
*
* 如果指定的比较器为 null,则此列表中的所有元素都必须实现 Comparable 接口,
* 并且应使用元素的自然顺序。
*
* 此列表必须是可修改的,但不必是可调整大小的。
*
* @param c 用于比较列表元素的比较器。
* null 值表示应使用元素的自然顺序
* @since 1.8
*/
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
// 对数组进行排序
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
源码解读
sort(Comparator<? super E> c)
方法用于对 ArrayList
中的元素进行排序。它接受一个 Comparator
类型的参数 c
,如果 c
为 null
,则使用元素的自然顺序进行排序;如果 c
不为 null
,则使用指定的比较器进行排序。
在排序之前,会记录当前的 modCount
值。然后调用 Arrays.sort
方法对 elementData
数组中从索引 0 到 size - 1
的元素进行排序。
排序完成后,会检查 modCount
是否发生了变化。如果发生了变化,说明在排序过程中列表的结构被修改了,会抛出 ConcurrentModificationException
异常。最后,增加 modCount
的值。
性能分析
sort
方法的时间复杂度取决于使用的排序算法。Arrays.sort
方法在 Java 中使用的是双轴快速排序(Dual-Pivot Quicksort)算法,平均时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n l o g n ) O(n log n) </math>O(nlogn),其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 是列表中元素的数量。
示例代码
java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class SortMethodExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 20));
people.add(new Person("Charlie", 30));
// 使用自定义比较器按年龄排序
people.sort(Comparator.comparingInt(Person::getAge));
for (Person person : people) {
System.out.println(person);
}
}
}
6.10 查找方法
6.10.1 indexOf(Object o)
和 lastIndexOf(Object o)
方法
java
/**
* 返回此列表中指定元素的第一个匹配项的索引,如果此列表不包含该元素,则返回 -1。
* 更正式地,返回最低索引 i,使得 (o==null ? get(i)==null : o.equals(get(i))),
* 或者如果没有这样的索引,则返回 -1。
*
* @param o 要搜索的元素
* @return 此列表中指定元素的第一个匹配项的索引,如果此列表不包含该元素,则返回 -1
*/
public int indexOf(Object o) {
if (o == null) {
// 遍历数组,查找第一个为 null 的元素
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
// 遍历数组,查找第一个与指定对象相等的元素
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 返回此列表中指定元素的最后一个匹配项的索引,如果此列表不包含该元素,则返回 -1。
* 更正式地,返回最高索引 i,使得 (o==null ? get(i)==null : o.equals(get(i))),
* 或者如果没有这样的索引,则返回 -1。
*
* @param o 要搜索的元素
* @return 此列表中指定元素的最后一个匹配项的索引,如果此列表不包含该元素,则返回 -1
*/
public int lastIndexOf(Object o) {
if (o == null) {
// 从数组末尾开始遍历,查找最后一个为 null 的元素
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
// 从数组末尾开始遍历,查找最后一个与指定对象相等的元素
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
源码解读
indexOf(Object o)
方法用于查找指定元素在 ArrayList
中第一次出现的索引。如果元素为 null
,则遍历数组,查找第一个为 null
的元素;否则,使用 equals
方法比较元素,查找第一个匹配的元素。如果找到匹配的元素,返回其索引;如果没有找到,返回 -1。
lastIndexOf(Object o)
方法用于查找指定元素在 ArrayList
中最后一次出现的索引。它与 indexOf
方法类似,只是从数组的末尾开始遍历。
性能分析
indexOf
和 lastIndexOf
方法的时间复杂度都为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),因为在最坏情况下,需要遍历整个数组才能找到元素。
示例代码
java
import java.util.ArrayList;
import java.util.List;
public class IndexOfExample {
public static void main(String[] args) {
// 创建一个 ArrayList 并添加元素
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple");
// 查找元素的第一次出现的索引
int firstIndex = fruits.indexOf("Apple");
System.out.println("Apple 第一次出现的索引: " + firstIndex);
// 查找元素的最后一次出现的索引
int lastIndex = fruits.lastIndexOf("Apple");
System.out.println("Apple 最后一次出现的索引: " + lastIndex);
}
}
6.11 子列表方法
6.11.1 subList(int fromIndex, int toIndex)
方法
java
/**
* 返回此列表中指定的 fromIndex(包含)和 toIndex(不包含)之间的部分的视图。
* (如果 fromIndex 和 toIndex 相等,则返回的列表为空。)
* 返回的列表由此列表支持,因此返回列表中的非结构性更改将反映在此列表中,反之亦然。
* 返回的列表支持此列表支持的所有可选列表操作。
*
* 此方法消除了对显式范围操作(对于数组通常存在的)的需要。
* 任何需要列表的操作都可以通过传递子列表视图而不是整个列表来作为范围操作。
* 例如,以下习语从列表中移除了一个范围的元素:
*
* list.subList(from, to).clear();
*
* 可以为 indexOf 和 lastIndexOf 构造类似的习语,并且 Collections 类中的所有算法都可以应用于子列表。
*
* (此方法与 String 类的 substring 方法类似。)
*
* @param fromIndex 子列表的起始索引(包含)
* @param toIndex 子列表的结束索引(不包含)
* @return 此列表中指定范围的视图
* @throws IndexOutOfBoundsException 对于非法的端点索引值
* (fromIndex < 0 || toIndex > size || fromIndex > toIndex)
*/
public List<E> subList(int fromIndex, int toIndex) {
// 检查索引是否合法
subListRangeCheck(fromIndex, toIndex, size);
// 创建一个 SubList 对象
return new SubList(this, 0, fromIndex, toIndex);
}
/**
* 检查子列表范围的索引是否合法。
*
* @param fromIndex 子列表的起始索引(包含)
* @param toIndex 子列表的结束索引(不包含)
* @param size 列表的大小
* @throws IndexOutOfBoundsException 对于非法的端点索引值
* (fromIndex < 0 || toIndex > size || fromIndex > toIndex)
*/
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
}
/**
* 子列表实现类
*/
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
public E set(int index, E e) {
// 检查索引是否越界
rangeCheck(index);
// 检查是否有并发修改
checkForComodification();
// 调用父列表的 set 方法设置元素
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData(offset + index) = e;
return oldValue;
}
public E get(int index) {
// 检查索引是否越界
rangeCheck(index);
// 检查是否有并发修改
checkForComodification();
// 调用父列表的 get 方法获取元素
return ArrayList.this.elementData(offset + index);
}
public int size() {
// 检查是否有并发修改
checkForComodification();
return this.size;
}
public void add(int index, E e) {
// 检查索引是否越界
rangeCheckForAdd(index);
// 检查是否有并发修改
checkForComodification();
// 调用父列表的 add 方法插入元素
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index) {
// 检查索引是否越界
rangeCheck(index);
// 检查是否有并发修改
checkForComodification();
// 调用父列表的 remove 方法移除元素
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
protected void removeRange(int fromIndex, int toIndex) {
// 检查是否有并发修改
checkForComodification();
// 调用父列表的 removeRange 方法移除元素范围
parent.removeRange(parentOffset + fromIndex,
parentOffset + toIndex);
this.modCount = parent.modCount;
this.size -= toIndex - fromIndex;
}
public boolean addAll(Collection<? extends E> c) {
return addAll(this.size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
// 检查索引是否越界
rangeCheckForAdd(index);
// 检查是否有
java
public boolean addAll(int index, Collection<? extends E> c) {
// 检查索引是否越界
rangeCheckForAdd(index);
// 检查是否有并发修改
checkForComodification();
// 调用父列表的 addAll 方法插入元素
boolean modified = parent.addAll(parentOffset + index, c);
this.modCount = parent.modCount;
this.size += c.size();
return modified;
}
public List<E> subList(int fromIndex, int toIndex) {
// 检查子列表范围的索引是否合法
subListRangeCheck(fromIndex, toIndex, size);
// 创建并返回一个新的子列表对象
return new SubList(this, offset, offset + fromIndex, offset + toIndex);
}
public boolean retainAll(Collection<?> c) {
return batchRemove(c, true);
}
public boolean removeAll(Collection<?> c) {
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
// 检查是否有并发修改
checkForComodification();
final int size = this.size;
int w = 0, i = 0;
boolean modified = false;
try {
for (; i < size; i++)
if (c.contains(get(i)) == complement)
// 保留或移除元素,根据complement参数决定
get(w++);
} finally {
if (i != size) {
// 将剩余元素向前移动
System.arraycopy(elementData, offset + i,
elementData, offset + w,
size - i);
w += size - i;
}
if (w != size) {
// 清空多余元素位置
for (int j = w; j < size; j++)
elementData[offset + j] = null;
this.modCount++;
this.size = w;
modified = true;
}
}
return modified;
}
public void clear() {
// 检查是否有并发修改
checkForComodification();
// 调用父列表的 removeRange 方法清空子列表
parent.removeRange(parentOffset, parentOffset + size);
this.modCount = parent.modCount;
this.size = 0;
}
@Override
public Spliterator<E> spliterator() {
// 检查是否有并发修改
checkForComodification();
return new SubListSpliterator<>(this, 0, -1, 0);
}
@Override
public void forEach(Consumer<? super E> action) {
// 检查是否有并发修改
Objects.requireNonNull(action);
checkForComodification();
final int size = this.size;
for (int i = 0; i < size; i++)
action.accept(get(i));
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
// 检查是否有并发修改
Objects.requireNonNull(operator);
checkForComodification();
final int size = this.size;
for (int i = 0; i < size; i++)
set(i, operator.apply(get(i)));
}
@Override
public void sort(Comparator<? super E> c) {
// 检查是否有并发修改
checkForComodification();
ArrayList.sort(c, subListRangeView());
this.modCount = ArrayList.this.modCount;
}
private ArrayList<E> subListRangeView() {
// 返回包含子列表元素的ArrayList
return new ArrayList<>(this);
}
@Override
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (get(i) == null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(get(i)))
return i;
}
return -1;
}
@Override
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size - 1; i >= 0; i--)
if (get(i) == null)
return i;
} else {
for (int i = size - 1; i >= 0; i--)
if (o.equals(get(i)))
return i;
}
return -1;
}
@Override
public Object[] toArray() {
// 检查是否有并发修改
checkForComodification();
Object[] r = new Object[size];
for (int i = 0; i < size; i++)
r[i] = get(i);
return r;
}
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(T[] a) {
// 检查是否有并发修改
checkForComodification();
if (a.length < size)
return (T[]) Arrays.copyOfRange(elementData, offset, offset + size, a.getClass());
System.arraycopy(elementData, offset, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void rangeCheckForAdd(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
return "Index: " + index + ", Size: " + size;
}
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
}
源码解读
subList(int fromIndex, int toIndex)
方法返回的 SubList
类是 ArrayList
的内部类,它继承自 AbstractList
并实现了 RandomAccess
接口。SubList
类的实例表示原 ArrayList
的一个子列表,这个子列表与原列表共享底层数据存储(elementData
数组),因此对 SubList
的非结构性修改(如修改元素值)会直接反映在原 ArrayList
中,反之亦然 。
- 构造函数 :
SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex)
构造函数接收父列表parent
、父列表中的偏移量parentOffset
、子列表在父列表中的起始索引fromIndex
和结束索引toIndex
。通过这些参数来确定子列表在原列表中的位置和范围,并初始化子列表的大小size
和修改计数modCount
(与原列表的modCount
保持一致)。
- 元素访问与修改方法 :
get(int index)
和set(int index, E e)
方法通过将子列表的索引index
加上offset
来映射到原列表中的实际索引,从而实现对原列表中对应位置元素的访问和修改。- 例如,
get(int index)
方法中,先进行索引越界检查和并发修改检查,然后通过ArrayList.this.elementData(offset + index)
从原列表中获取元素。
- 结构修改方法 :
add(int index, E e)
、remove(int index)
等方法都是通过调用父列表(原ArrayList
)的对应方法,并根据子列表在父列表中的偏移量来确定操作的实际位置 。比如add(int index, E e)
方法调用parent.add(parentOffset + index, e)
向原列表中插入元素,并更新子列表的大小和修改计数。clear()
方法调用父列表的removeRange
方法,一次性移除子列表在原列表中对应的元素范围,实现子列表的清空操作。
- 批量操作方法 :
retainAll
、removeAll
方法调用batchRemove
方法实现。batchRemove
方法与原ArrayList
中的batchRemove
逻辑类似,通过遍历子列表元素,根据complement
参数决定保留或移除元素,并在操作完成后更新子列表的状态(如修改计数、大小等)。
- 其他方法 :
indexOf
和lastIndexOf
方法通过遍历子列表元素,与目标元素进行比较,找到元素在子列表中的索引位置。由于子列表与原列表共享数据,这里的查找本质上是在原列表的对应范围内进行。toArray
系列方法用于将子列表元素转换为数组,同样是基于原列表的elementData
数组,根据子列表的范围进行元素复制或转换操作。
性能分析
- 元素访问 :
get
和set
方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为它们只是通过简单的索引计算直接访问原列表中的元素,类似于原ArrayList
的随机访问特性。 - 结构修改 :
add
和remove
方法的时间复杂度在最坏情况下为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),其中n
是原ArrayList
的大小。因为在插入或移除元素时,可能需要移动原列表中后续的元素来保持连续性。 - 批量操作 :
retainAll
、removeAll
等批量操作方法的时间复杂度取决于子列表的大小以及操作涉及的元素数量。在最坏情况下,遍历子列表和判断元素是否保留/移除的操作,时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( m ) O(m) </math>O(m),其中m
是子列表的大小;如果涉及元素移动(如移除元素后整理数组),则还可能与原列表大小相关 。 - 查找操作 :
indexOf
和lastIndexOf
方法在最坏情况下需要遍历子列表中的所有元素,时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( m ) O(m) </math>O(m),其中m
是子列表的大小。
示例代码
java
import java.util.ArrayList;
import java.util.List;
public class SubListExample {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
numbers.add(i);
}
// 获取子列表,包含索引 2 到 6(不包含 6)的元素
List<Integer> subList = numbers.subList(2, 6);
// 修改子列表中的元素
subList.set(1, 100);
// 打印原列表,会发现原列表对应位置的元素也被修改
System.out.println("原列表: " + numbers);
// 向子列表中添加元素
subList.add(200);
// 再次打印原列表,会看到原列表的结构和元素都发生了变化
System.out.println("修改后的原列表: " + numbers);
}
}
七、ArrayList 的容量管理
7.1 自动扩容机制
ArrayList
的自动扩容机制是其能够动态存储元素的核心特性。当向 ArrayList
中添加元素时,如果当前数组容量不足,就会触发扩容操作,以确保有足够的空间容纳新元素。
java
private void ensureCapacityInternal(int minCapacity) {
// 计算所需的最小容量
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 如果是使用无参构造函数创建的 ArrayList,返回默认容量和所需最小容量的较大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 否则直接返回所需最小容量
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 记录修改次数,modCount 用于迭代器的并发修改检查
modCount++;
// 如果所需最小容量大于当前数组的长度,说明容量不足,需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// 获取当前数组的容量
int oldCapacity = elementData.length;
// 计算新的容量,新容量为旧容量的 1.5 倍(oldCapacity + (oldCapacity >> 1) 等价于 oldCapacity * 1.5)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新容量小于所需最小容量,将新容量设置为所需最小容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新容量大于最大数组容量,调用 hugeCapacity 方法处理
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 使用 Arrays.copyOf 方法将原数组的元素复制到新数组中,完成扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 防止溢出
throw new OutOfMemoryError();
// 如果所需最小容量大于最大数组容量,返回 Integer.MAX_VALUE,否则返回最大数组容量
return (minCapacity > MAX_ARRAY_SIZE)?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
源码解读
- 容量计算 :
calculateCapacity
方法用于计算所需的最小容量。如果ArrayList
是通过无参构造函数创建的(此时elementData
为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
),则返回默认容量DEFAULT_CAPACITY
(值为 10)和实际所需最小容量minCapacity
中的较大值。否则,直接返回minCapacity
。
- 容量检查与扩容触发 :
ensureCapacityInternal
方法调用calculateCapacity
计算最小容量后,将结果传递给ensureExplicitCapacity
方法。ensureExplicitCapacity
方法首先增加modCount
,记录列表结构的修改。然后比较所需最小容量minCapacity
和当前数组的长度elementData.length
。如果minCapacity
大于elementData.length
,说明当前数组容量不足,需要调用grow
方法进行扩容。
- 扩容实现 :
grow
方法是扩容的核心逻辑。首先获取当前数组的容量oldCapacity
,然后计算新的容量newCapacity
,新容量为旧容量的 1.5 倍。接着检查新容量是否满足最小容量需求,如果不满足,则将新容量设置为minCapacity
。- 再检查新容量是否超过最大数组容量
MAX_ARRAY_SIZE
,如果超过,则调用hugeCapacity
方法进行处理。hugeCapacity
方法会根据minCapacity
的值返回Integer.MAX_VALUE
或MAX_ARRAY_SIZE
。 - 最后,通过
Arrays.copyOf
方法创建一个新的数组,并将原数组的元素复制到新数组中,完成扩容操作,同时更新elementData
引用指向新数组。
性能分析
- 时间复杂度 :扩容操作中,
Arrays.copyOf
方法用于复制数组元素,其时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),其中n
是原数组的元素数量。因此,每次扩容操作的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)。在频繁添加元素导致多次扩容的情况下,虽然单次扩容时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),但由于每次扩容后容量增大,整体平均时间复杂度摊还下来接近 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1) ,这也是ArrayList
能够高效动态存储元素的原因。 - 空间复杂度 :扩容后数组的容量会增加,在扩容过程中,会创建一个新的更大的数组,并将原数组元素复制过去,因此在扩容瞬间会占用额外的空间。不过从长期来看,
ArrayList
通过动态调整容量,使得空间利用相对合理 ,平均空间复杂度接近存储元素的实际空间需求。
示例代码
java
import java.util.ArrayList;
public class ArrayListCapacityExample {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 15; i++) {
list.add(i);
// 打印每次添加元素后的数组容量
System.out.println("添加元素 " + i + " 后,数组容量: " + java.lang.reflect.Array.getLength(list.toArray()));
}
}
}
7.2 手动扩容方法
ArrayList
提供了 ensureCapacity
方法,允许开发者手动指定数组的最小容量,从而避免频繁的自动扩容,提升性能。
java
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
// 如果指定的最小容量大于最小扩展容量,进行容量检查和扩容
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
源码解读
ensureCapacity
方法允许用户手动指定 ArrayList
所需的最小容量,从而避免在添加元素时频繁触发自动扩容机制,以提高性能。以下是对该方法的详细解读:
java
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
// 如果指定的最小容量大于最小扩展容量,进行容量检查和扩容
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
-
计算最小扩展容量:
minExpand
变量用于存储最小扩展容量。如果elementData
不等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,说明ArrayList
不是通过无参构造函数创建的,minExpand
被赋值为 0。- 若
elementData
等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,表示ArrayList
是通过无参构造函数创建的,minExpand
被赋值为DEFAULT_CAPACITY
(默认值为 10)。
-
检查并执行扩容:
- 比较用户指定的
minCapacity
和minExpand
。若minCapacity
大于minExpand
,则调用ensureExplicitCapacity(minCapacity)
方法进行容量检查和可能的扩容操作。 ensureExplicitCapacity
方法在之前自动扩容机制部分已详细分析,它会记录修改次数modCount
,并判断是否需要调用grow
方法进行扩容。
- 比较用户指定的
性能分析
- 时间复杂度 :如果指定的
minCapacity
小于或等于当前ArrayList
的容量,该方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1),因为只进行简单的比较操作。若minCapacity
大于当前容量,会触发扩容操作,时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),其中n
是当前ArrayList
中的元素数量。 - 空间复杂度 :当触发扩容时,会创建一个更大的数组,空间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)。若未触发扩容,空间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1)。
示例代码
java
import java.util.ArrayList;
public class ManualCapacityExample {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
// 手动设置最小容量为 20
list.ensureCapacity(20);
for (int i = 0; i < 15; i++) {
list.add(i);
System.out.println("添加元素 " + i + " 后,数组容量: " + java.lang.reflect.Array.getLength(list.toArray()));
}
}
}
7.3 缩容方法
ArrayList
提供了 trimToSize
方法,用于将 ArrayList
的容量调整为当前元素的实际数量,以节省内存空间。
java
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
源码解读
java
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
-
记录修改次数:
- 首先增加
modCount
,表示列表的结构发生了修改。modCount
用于在迭代器操作时检测并发修改异常。
- 首先增加
-
判断是否需要缩容:
- 比较
size
(当前ArrayList
中元素的实际数量)和elementData.length
(当前数组的容量)。如果size
小于elementData.length
,说明数组存在多余的空间,需要进行缩容操作。
- 比较
-
执行缩容操作:
- 如果
size
等于 0,将elementData
赋值为EMPTY_ELEMENTDATA
,即一个空数组。 - 若
size
大于 0,使用Arrays.copyOf
方法创建一个新数组,新数组的长度为size
,并将原数组中前size
个元素复制到新数组中,然后将elementData
引用指向新数组。
- 如果
性能分析
- 时间复杂度 :该方法的时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),其中
n
是当前ArrayList
中的元素数量。因为需要复制数组元素。 - 空间复杂度 :缩容后,数组的容量会减小到
size
,空间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)。
示例代码
java
import java.util.ArrayList;
public class TrimToSizeExample {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>(20);
for (int i = 0; i < 5; i++) {
list.add(i);
}
System.out.println("缩容前数组容量: " + java.lang.reflect.Array.getLength(list.toArray()));
list.trimToSize();
System.out.println("缩容后数组容量: " + java.lang.reflect.Array.getLength(list.toArray()));
}
}
八、ArrayList 的线程安全性
8.1 线程不安全原因
ArrayList
是非线程安全的,主要原因在于其方法没有进行同步处理,多个线程同时对 ArrayList
进行读写操作时,可能会导致数据不一致、数组越界等问题。
例如,当多个线程同时调用 add
方法时,可能会出现以下情况:
- 数据覆盖:两个线程同时检测到数组容量足够,然后都尝试向数组的同一位置添加元素,导致其中一个元素被覆盖。
- 数组越界:在自动扩容过程中,如果多个线程同时进行添加操作,可能会导致扩容逻辑混乱,从而引发数组越界异常。
8.2 线程安全解决方案
8.2.1 使用 Collections.synchronizedList
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SynchronizedListExample {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
// 使用 Collections.synchronizedList 将 ArrayList 转换为线程安全的列表
List<Integer> synchronizedList = Collections.synchronizedList(list);
// 在多线程环境下使用 synchronizedList
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
synchronizedList.add(i);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 100; i < 200; i++) {
synchronizedList.add(i);
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("列表大小: " + synchronizedList.size());
}
}
Collections.synchronizedList
方法返回一个线程安全的列表,它在内部对所有的操作都进行了同步处理,通过 synchronized
关键字保证同一时间只有一个线程可以访问列表。
8.2.2 使用 CopyOnWriteArrayList
java
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
Thread writer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(i);
}
});
Thread reader = new Thread(() -> {
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
});
writer.start();
reader.start();
try {
writer.join();
reader.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CopyOnWriteArrayList
是 Java 并发包中的线程安全列表。它的写操作(如 add
、remove
等)会先复制一份原数组,在新数组上进行修改,然后将原数组的引用指向新数组。读操作则直接在原数组上进行,因此读操作不需要加锁,不会阻塞其他线程的读操作。这种方式保证了读写操作的线程安全性,但写操作的性能相对较低,因为需要复制数组。
九、ArrayList 的使用场景和注意事项
9.1 使用场景
- 随机访问频繁 :由于
ArrayList
基于数组实现,支持随机访问,通过索引可以快速定位元素,时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1)。因此,当需要频繁进行随机访问操作时,ArrayList
是一个很好的选择,例如在遍历列表中的元素、根据索引获取元素等场景。 - 元素数量相对稳定 :虽然
ArrayList
支持动态扩容,但频繁的扩容操作会影响性能。如果元素数量在创建列表后相对稳定,或者可以提前预估元素数量并手动设置初始容量,ArrayList
可以高效地存储和管理元素。
9.2 注意事项
- 容量管理 :为了避免频繁的自动扩容,在创建
ArrayList
时,如果能预估元素数量,建议使用带初始容量的构造函数,或者使用ensureCapacity
方法手动设置最小容量。同时,在元素数量减少后,如果需要节省内存,可以使用trimToSize
方法进行缩容。 - 线程安全 :在多线程环境下,
ArrayList
是非线程安全的。如果需要在多线程中使用列表,建议使用Collections.synchronizedList
或CopyOnWriteArrayList
来保证线程安全。 - 内存占用 :由于
ArrayList
是基于数组实现的,即使元素数量较少,数组也会占用一定的内存空间。如果元素数量变化较大且不确定,可能会导致内存浪费。在这种情况下,可以考虑使用其他数据结构,如LinkedList
。