Java List 集合详解
List是Java集合框架中最核心的接口之一,它继承自Collection接口,提供了有序、可重复、可索引的集合操作能力。在Java开发中,List集合被广泛应用于各种场景,从简单的数据存储到复杂的业务逻辑处理,都离不开List的支持。
List接口 = 有序性 + 可重复性 + 可索引性 + 可修改性 + 批量操作支持
- 📋 有序性:元素按照插入顺序排列,位置可预测
- 🔄 可重复性:允许添加相同的元素,不强制唯一性
- 🔢 可索引性:支持通过索引快速定位和访问元素(如get(i))
- ✏️ 可修改性:支持在任意位置添加、删除、替换元素
- 🛠️ 批量操作:支持子列表视图和整体集合操作
1. List接口基础概念
1.1 什么是List接口?
List接口是Java集合框架中的核心接口,它继承自Collection接口,为有序集合提供了完整的抽象。
List集合具有以下核心特征:
- 有序性:元素按照插入顺序存储,可以通过索引访问
- 可重复性:允许存储重复的元素
- 可索引性:支持基于位置的元素访问和操作
- 可修改性:支持元素的增删改查操作
- 批量操作:支持集合级别的批量处理
1.2 List接口的重要性
| 重要性 | 具体体现 | 业务价值 |
|---|---|---|
| 数据组织 | 提供有序的数据存储结构 | 支持业务逻辑的顺序性要求 |
| 快速访问 | 基于索引的O(1)随机访问 | 提高数据检索效率 |
| 灵活操作 | 支持位置相关的增删改查 | 满足复杂的业务操作需求 |
| 框架基础 | 为其他集合类型提供基础 | 构建完整的集合体系 |
1.3 List接口设计原则
List接口的设计遵循以下几个核心原则:
位置访问原则
提供基于索引的元素访问能力,支持随机访问模式
位置操作原则
支持在指定位置插入和删除元素,满足精确控制需求
批量操作原则
支持集合级别的批量处理,提高操作效率
视图操作原则
提供子列表视图,支持范围操作和分段处理
1public interface List<E> extends Collection<E> {2 3 // ========== 位置访问 ==========4 E get(int index); // 获取指定位置的元素5 E set(int index, E element); // 设置指定位置的元素6 7 // ========== 位置操作 ==========8 void add(int index, E element); // 在指定位置插入元素9 E remove(int index); // 删除指定位置的元素10 11 // ========== 批量操作 ==========12 boolean addAll(Collection<? extends E> c); // 批量添加13 boolean addAll(int index, Collection<? extends E> c); // 指定位置批量添加14 15 // ========== 视图操作 ==========16 List<E> subList(int fromIndex, int toIndex); // 获取子列表视图17}1.4 List接口核心方法详解
List接口提供了丰富的方法来操作有序集合,这些方法可以分为几个主要类别:
- 基本操作方法
- 位置操作方法
- 批量操作方法
- 迭代器方法
1public interface List<E> extends Collection<E> {2 3 // 添加和删除元素4 boolean add(E e); // 添加元素到末尾5 boolean remove(Object o); // 删除指定元素6 7 // 位置访问8 E get(int index); // 获取指定位置元素9 E set(int index, E element); // 设置指定位置元素10 11 // 集合信息12 int size(); // 获取集合大小13 boolean isEmpty(); // 判断是否为空14 boolean contains(Object o); // 判断是否包含元素15 void clear(); // 清空集合16}| 方法 | 描述 | 返回值 | 时间复杂度 |
|---|---|---|---|
add(E e) | 添加元素到末尾 | 成功返回true | O(1)~O(n) |
remove(Object o) | 删除第一个匹配元素 | 成功返回true | O(n) |
get(int index) | 获取指定位置元素 | 元素值 | O(1)~O(n) |
set(int index, E element) | 设置指定位置元素 | 原元素值 | O(1)~O(n) |
1public interface List<E> extends Collection<E> {2 3 // 位置插入和删除4 void add(int index, E element); // 在指定位置插入5 E remove(int index); // 删除指定位置元素6 7 // 位置查找8 int indexOf(Object o); // 查找元素第一次出现位置9 int lastIndexOf(Object o); // 查找元素最后一次出现位置10}| 方法 | 描述 | 返回值 | 时间复杂度 |
|---|---|---|---|
add(int index, E element) | 在指定位置插入元素 | void | O(n) |
remove(int index) | 删除指定位置元素 | 被删除的元素 | O(n) |
indexOf(Object o) | 查找第一次出现位置 | 索引值或-1 | O(n) |
lastIndexOf(Object o) | 查找最后一次出现位置 | 索引值或-1 | O(n) |
1public interface List<E> extends Collection<E> {2 3 // 批量添加4 boolean addAll(Collection<? extends E> c); // 末尾批量添加5 boolean addAll(int index, Collection<? extends E> c); // 指定位置批量添加6 7 // 批量删除8 boolean removeAll(Collection<?> c); // 删除指定集合中的元素9 boolean retainAll(Collection<?> c); // 保留指定集合中的元素10 11 // 视图操作12 List<E> subList(int fromIndex, int toIndex); // 获取子列表视图13}| 方法 | 描述 | 返回值 | 时间复杂度 |
|---|---|---|---|
addAll(Collection<? extends E> c) | 在末尾批量添加 | 成功返回true | O(n) |
addAll(int index, Collection<? extends E> c) | 在指定位置批量添加 | 成功返回true | O(n) |
removeAll(Collection<?> c) | 移除所有包含元素 | 成功返回true | O(n²) |
retainAll(Collection<?> c) | 仅保留共有元素 | 成功返回true | O(n²) |
subList(int fromIndex, int toIndex) | 获取子列表视图 | List视图 | O(1) |
1public interface List<E> extends Collection<E> {2 3 // 列表迭代器4 ListIterator<E> listIterator(); // 获取列表迭代器5 ListIterator<E> listIterator(int index); // 从指定位置开始的迭代器6 7 // 普通迭代器(继承自Collection)8 Iterator<E> iterator(); // 获取迭代器9}| 方法 | 描述 | 返回值 | 特殊功能 |
|---|---|---|---|
iterator() | 获取普通迭代器 | Iterator对象 | 单向遍历、remove() |
listIterator() | 获取列表迭代器 | ListIterator对象 | 双向遍历、添加元素 |
listIterator(int index) | 从指定位置开始的迭代器 | ListIterator对象 | 从任意位置开始 |
ListIterator是List接口的特有迭代器,相比普通Iterator具有更多功能:
hasPrevious()/previous():支持双向遍历add(E e):在当前位置添加元素set(E e):替换最后返回的元素nextIndex()/previousIndex():获取元素索引
1.5 List接口方法分类对比
| 方法类别 | 主要方法 | 时间复杂度 | 使用场景 |
|---|---|---|---|
| 基本操作 | add(E), remove(Object), get(int), set(int) | O(1) - O(n) | 日常的增删改查操作 |
| 位置操作 | add(int, E), remove(int), indexOf(Object) | O(1) - O(n) | 需要精确位置控制的操作 |
| 批量操作 | addAll(Collection), subList(int, int) | O(n) | 批量数据处理和范围操作 |
| 迭代器 | listIterator(), listIterator(int) | O(1) | 双向遍历和修改操作 |
- 随机访问操作(get、set)在ArrayList中为O(1),在LinkedList中为O(n)
- 位置插入删除操作在ArrayList中为O(n),在LinkedList中为O(1)
- 选择实现类时应根据主要操作类型进行权衡
2. List 实现类详解
- ArrayList 实现
- LinkedList 实现
- Vector/Stack 实现
2.1 ArrayList 概述
ArrayList是List接口最常用的实现类,基于动态数组实现,具有以下特点:
- 🔄 动态扩容:自动调整容量以适应元素数量变化
- ⚡ 随机访问:基于索引的快速访问,时间复杂度O(1)
- 🚀 尾部操作高效:在列表末尾添加和删除元素效率很高
- 📦 内存连续:元素在内存中连续存储,缓存友好
- ⚠️ 线程不安全:在多线程环境下需要外部同步
适用场景
- 频繁的随机访问操作
- 元素数量相对稳定的场景
- 需要高性能读取的应用
- 内存使用要求不严格的场景
2.2 ArrayList 内部结构
ArrayList基于动态数组实现,内部维护一个Object数组来存储元素。当数组容量不足时,会自动创建更大的数组并复制原有元素。
核心字段
1public class ArrayList<E> extends AbstractList<E>2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable {3 4 // 默认初始容量5 private static final int DEFAULT_CAPACITY = 10;6 7 // 空数组实例,用于空实例8 private static final Object[] EMPTY_ELEMENTDATA = {};9 10 // 默认大小的空数组实例11 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};12 13 // 存储元素的数组缓冲区14 transient Object[] elementData;15 16 // ArrayList的大小(包含的元素数量)17 private int size;18}构造方法
1public class ArrayList<E> extends AbstractList<E> {2 3 /**4 * 构造一个初始容量为10的空列表5 */6 public ArrayList() {7 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;8 }9 10 /**11 * 构造一个指定初始容量的空列表12 */13 public ArrayList(int initialCapacity) {14 if (initialCapacity > 0) {15 this.elementData = new Object[initialCapacity];16 } else if (initialCapacity == 0) {17 this.elementData = EMPTY_ELEMENTDATA;18 } else {19 throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);20 }21 }22 23 /**24 * 构造一个包含指定集合元素的列表25 */26 public ArrayList(Collection<? extends E> c) {27 elementData = c.toArray();28 if ((size = elementData.length) != 0) {29 if (elementData.getClass() != Object[].class) {30 elementData = Arrays.copyOf(elementData, size, Object[].class);31 }32 } else {33 this.elementData = EMPTY_ELEMENTDATA;34 }35 }36}内存布局示意图
1ArrayList 实例2┌─────────────────────────────────────────┐3│ elementData: Object[] │4│ ├── [0] → "Element1" │5│ ├── [1] → "Element2" │6│ ├── [2] → "Element3" │7│ ├── [3] → null │8│ └── [4] → null │9│ size: 3 │10│ modCount: 1 │11└─────────────────────────────────────────┘2.3 ArrayList 核心方法实现
2.3.1 添加元素
ArrayList的添加操作包括在末尾添加和在指定位置插入两种方式:
1public class ArrayList<E> extends AbstractList<E> {2 3 /**4 * 在列表末尾添加元素5 * 时间复杂度:平均O(1),最坏情况O(n)(需要扩容)6 */7 public boolean add(E e) {8 // 确保容量足够9 ensureCapacityInternal(size + 1);10 // 在末尾添加元素11 elementData[size++] = e;12 return true;13 }14 15 /**16 * 在指定位置插入元素17 * 时间复杂度:O(n),需要移动后续元素18 */19 public void add(int index, E element) {20 // 检查索引范围21 rangeCheckForAdd(index);22 // 确保容量足够23 ensureCapacityInternal(size + 1);24 // 移动后续元素25 System.arraycopy(elementData, index, elementData, index + 1, size - index);26 // 插入新元素27 elementData[index] = element;28 size++;29 }30}2.3.2 扩容机制
当数组容量不足时,ArrayList会自动扩容:
1public class ArrayList<E> extends AbstractList<E> {2 3 /**4 * 确保内部容量足够5 */6 private void ensureCapacityInternal(int minCapacity) {7 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {8 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);9 }10 ensureExplicitCapacity(minCapacity);11 }12 13 /**14 * 确保显式容量足够15 */16 private void ensureExplicitCapacity(int minCapacity) {17 modCount++;18 if (minCapacity - elementData.length > 0) {19 grow(minCapacity);20 }21 }22 23 /**24 * 扩容核心方法25 */26 private void grow(int minCapacity) {27 int oldCapacity = elementData.length;28 // 新容量 = 旧容量 + 旧容量/2(1.5倍增长)29 int newCapacity = oldCapacity + (oldCapacity >> 1);30 31 if (newCapacity - minCapacity < 0) {32 newCapacity = minCapacity;33 }34 35 if (newCapacity - MAX_ARRAY_SIZE > 0) {36 newCapacity = hugeCapacity(minCapacity);37 }38 39 // 创建新数组并复制元素40 elementData = Arrays.copyOf(elementData, newCapacity);41 }42}- 默认初始容量:10
- 扩容倍数:1.5倍(oldCapacity + oldCapacity >> 1)
- 最大容量:Integer.MAX_VALUE - 8
- 扩容操作会创建新数组并复制元素,开销较大
2.3.3 删除元素
ArrayList的删除操作包括按值删除和按位置删除两种方式:
1public class ArrayList<E> extends AbstractList<E> {2 3 /**4 * 删除指定位置的元素5 * 时间复杂度:O(n),需要移动后续元素6 */7 public E remove(int index) {8 rangeCheck(index);9 modCount++;10 E oldValue = elementData(index);11 12 int numMoved = size - index - 1;13 if (numMoved > 0) {14 // 移动后续元素15 System.arraycopy(elementData, index + 1, elementData, index, numMoved);16 }17 elementData[--size] = null; // 清除引用,帮助GC18 19 return oldValue;20 }21 22 /**23 * 删除指定元素的第一个出现24 * 时间复杂度:O(n),需要遍历查找25 */26 public boolean remove(Object o) {27 if (o == null) {28 for (int index = 0; index < size; index++) {29 if (elementData[index] == null) {30 fastRemove(index);31 return true;32 }33 }34 } else {35 for (int index = 0; index < size; index++) {36 if (o.equals(elementData[index])) {37 fastRemove(index);38 return true;39 }40 }41 }42 return false;43 }44 45 /**46 * 快速删除(不进行边界检查)47 */48 private void fastRemove(int index) {49 modCount++;50 int numMoved = size - index - 1;51 if (numMoved > 0) {52 System.arraycopy(elementData, index + 1, elementData, index, numMoved);53 }54 elementData[--size] = null; // 清除引用,帮助GC55 }56}- 按位置删除:时间复杂度O(n),需要移动后续元素
- 按值删除:时间复杂度O(n),需要遍历查找元素
- 批量删除:可以使用removeIf()方法提高效率
- 内存管理:删除后将引用设为null,帮助垃圾回收
2.4 ArrayList 性能分析
2.4.1 时间复杂度对比
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 随机访问 | O(1) | 基于数组索引直接访问 |
| 末尾添加 | 平均O(1) | 最坏情况O(n)(扩容时) |
| 位置插入 | O(n) | 需要移动后续元素 |
| 位置删除 | O(n) | 需要移动后续元素 |
| 按值删除 | O(n) | 需要遍历查找元素 |
| 查找元素 | O(n) | 需要遍历比较 |
2.4.2 空间复杂度
- 存储开销:每个元素占用一个引用空间
- 扩容开销:扩容时会创建新数组,临时占用双倍内存
- 内存碎片:删除元素后可能产生内存碎片
2.4.3 性能优化建议
1public class ArrayListOptimization {2 3 public static void main(String[] args) {4 // 1. 预分配容量,避免频繁扩容5 ArrayList<String> list1 = new ArrayList<>(1000);6 7 // 2. 批量操作,减少方法调用开销8 List<String> batchData = Arrays.asList("A", "B", "C", "D");9 list1.addAll(batchData);10 11 // 3. 使用迭代器进行删除操作12 Iterator<String> iterator = list1.iterator();13 while (iterator.hasNext()) {14 String item = iterator.next();15 if (item.equals("B")) {16 iterator.remove(); // 避免ConcurrentModificationException17 }18 }19 20 // 4. 使用removeIf进行条件删除21 list1.removeIf(item -> item.equals("C"));22 23 // 5. 使用trimToSize()释放多余空间24 list1.trimToSize();25 }26}2.5 ArrayList 使用示例
2.5.1 基本操作示例
1public class ArrayListBasicExample {2 3 public static void main(String[] args) {4 // 创建ArrayList5 List<String> list = new ArrayList<>();6 7 // 添加元素8 list.add("Java");9 list.add("Python");10 list.add("C++");11 12 // 访问元素13 String first = list.get(0);14 System.out.println("第一个元素: " + first);15 16 // 修改元素17 list.set(1, "JavaScript");18 19 // 删除元素20 list.remove("C++");21 22 // 遍历方式1:增强for循环23 for (String item : list) {24 System.out.println(item);25 }26 27 // 遍历方式2:索引遍历28 for (int i = 0; i < list.size(); i++) {29 System.out.println("索引 " + i + ": " + list.get(i));30 }31 32 // 遍历方式3:迭代器33 Iterator<String> iterator = list.iterator();34 while (iterator.hasNext()) {35 System.out.println(iterator.next());36 }37 38 // 遍历方式4:Lambda表达式39 list.forEach(System.out::println);40 }41}2.5.2 高级操作示例
1public class ArrayListAdvancedExample {2 3 public static void main(String[] args) {4 List<String> list = new ArrayList<>();5 6 // 批量添加7 list.addAll(Arrays.asList("A", "B", "C", "D", "E"));8 9 // 条件删除10 list.removeIf(item -> item.startsWith("A"));11 12 // 条件替换13 list.replaceAll(String::toLowerCase);14 15 // 排序16 list.sort(String::compareTo);17 18 // 子列表操作19 List<String> subList = list.subList(1, 3);20 subList.set(0, "NEW");21 22 System.out.println("原列表: " + list);23 System.out.println("子列表: " + subList);24 }25}3.1 LinkedList 概述
LinkedList是List接口的另一个重要实现类,基于双向链表实现,具有以下特点:
- 🔗 双向链表:每个节点都有前后指针,支持双向遍历
- ⚡ 插入删除高效:在任意位置插入删除元素时间复杂度都是O(1)
- 🐢 随机访问慢:基于索引访问需要遍历链表,时间复杂度O(n)
- 🔄 队列操作:实现了Deque接口,支持队列和栈操作
- 📊 内存分散:元素在内存中分散存储,缓存不友好
适用场景
- 频繁的插入删除操作
- 需要实现队列或栈功能
- 元素数量变化较大的场景
- 对随机访问性能要求不高的应用
3.2 LinkedList 内部结构
LinkedList基于双向链表实现,每个节点都包含对前一个和后一个节点的引用,支持双向遍历。
核心字段
1public class LinkedList<E> extends AbstractSequentialList<E>2 implements List<E>, Deque<E>, Cloneable, java.io.Serializable {3 4 // 双向链表节点5 private static class Node<E> {6 E item; // 存储的元素7 Node<E> next; // 下一个节点8 Node<E> prev; // 上一个节点9 10 Node(Node<E> prev, E element, Node<E> next) {11 this.item = element;12 this.next = next;13 this.prev = prev;14 }15 }16 17 // 头节点(第一个元素)18 transient Node<E> first;19 20 // 尾节点(最后一个元素)21 transient Node<E> last;22 23 // 链表中的元素个数24 transient int size = 0;25}构造方法
1public class LinkedList<E> extends AbstractSequentialList<E> {2 3 /**4 * 构造一个空的双向链表5 */6 public LinkedList() {}7 8 /**9 * 构造一个包含指定集合元素的链表10 */11 public LinkedList(Collection<? extends E> c) {12 this();13 addAll(c);14 }15}内存布局示意图
1LinkedList 实例2┌─────────────────────────────────────────┐3│ first: Node<E> │4│ last: Node<E> │5│ size: 3 │6│ modCount: 1 │7└─────────────────────────────────────────┘8 │ │9 ▼ ▼10 ┌─────────┐ ┌─────────┐ ┌─────────┐11 │ Node 1 │◄──►│ Node 2 │◄──►│ Node 3 │12 │ item: A │ │ item: B │ │ item: C │13 │ prev: │ │ prev: 1 │ │ prev: 2 │14 │ next: 2 │ │ next: 3 │ │ next: │15 └─────────┘ └─────────┘ └─────────┘3.3 LinkedList 核心方法实现
3.3.1 添加元素
1public class LinkedList<E> extends AbstractSequentialList<E> {2 3 /**4 * 在链表头部添加元素5 * 时间复杂度:O(1)6 */7 private void linkFirst(E e) {8 final Node<E> f = first;9 final Node<E> newNode = new Node<>(null, e, f);10 first = newNode;11 if (f == null) {12 last = newNode; // 如果链表为空13 } else {14 f.prev = newNode; // 设置原头节点的前驱15 }16 size++;17 modCount++;18 }19 20 /**21 * 在链表尾部添加元素22 * 时间复杂度:O(1)23 */24 void linkLast(E e) {25 final Node<E> l = last;26 final Node<E> newNode = new Node<>(l, e, null);27 last = newNode;28 if (l == null) {29 first = newNode; // 如果链表为空30 } else {31 l.next = newNode; // 设置原尾节点的后继32 }33 size++;34 modCount++;35 }36 37 /**38 * 在指定节点前插入新元素39 * 时间复杂度:O(1)40 */41 void linkBefore(E e, Node<E> succ) {42 final Node<E> pred = succ.prev;43 final Node<E> newNode = new Node<>(pred, e, succ);44 succ.prev = newNode;45 if (pred == null) {46 first = newNode; // 如果前驱为空,新节点成为头节点47 } else {48 pred.next = newNode; // 设置前驱节点的后继49 }50 size++;51 modCount++;52 }53}3.3.2 删除元素
1public class LinkedList<E> extends AbstractSequentialList<E> {2 3 /**4 * 删除头节点5 * 时间复杂度:O(1)6 */7 private E unlinkFirst(Node<E> f) {8 final E element = f.item;9 final Node<E> next = f.next;10 f.item = null;11 f.next = null; // 帮助GC12 first = next;13 if (next == null) {14 last = null; // 如果链表变为空15 } else {16 next.prev = null; // 新头节点的前驱设为null17 }18 size--;19 modCount++;20 return element;21 }22 23 /**24 * 删除尾节点25 * 时间复杂度:O(1)26 */27 private E unlinkLast(Node<E> l) {28 final E element = l.item;29 final Node<E> prev = l.prev;30 l.item = null;31 l.prev = null; // 帮助GC32 last = prev;33 if (prev == null) {34 first = null; // 如果链表变为空35 } else {36 prev.next = null; // 新尾节点的后继设为null37 }38 size--;39 modCount++;40 return element;41 }42 43 /**44 * 删除指定节点45 * 时间复杂度:O(1)46 */47 E unlink(Node<E> x) {48 final E element = x.item;49 final Node<E> next = x.next;50 final Node<E> prev = x.prev;51 52 if (prev == null) {53 first = next; // 如果前驱为空,后继成为头节点54 } else {55 prev.next = next; // 前驱的后继指向后继56 x.prev = null; // 帮助GC57 }58 59 if (next == null) {60 last = prev; // 如果后继为空,前驱成为尾节点61 } else {62 next.prev = prev; // 后继的前驱指向前驱63 x.next = null; // 帮助GC64 }65 66 x.item = null; // 帮助GC67 size--;68 modCount++;69 return element;70 }71}3.3.3 查找元素
1public class LinkedList<E> extends AbstractSequentialList<E> {2 3 /**4 * 根据索引查找节点5 * 时间复杂度:O(n)6 */7 Node<E> node(int index) {8 // 如果索引在前半部分,从头开始查找9 if (index < (size >> 1)) {10 Node<E> x = first;11 for (int i = 0; i < index; i++) {12 x = x.next;13 }14 return x;15 } else {16 // 如果索引在后半部分,从尾开始查找17 Node<E> x = last;18 for (int i = size - 1; i > index; i--) {19 x = x.prev;20 }21 return x;22 }23 }24 25 /**26 * 获取指定位置的元素27 * 时间复杂度:O(n)28 */29 public E get(int index) {30 checkElementIndex(index);31 return node(index).item;32 }33 34 /**35 * 设置指定位置的元素36 * 时间复杂度:O(n)37 */38 public E set(int index, E element) {39 checkElementIndex(index);40 Node<E> x = node(index);41 E oldVal = x.item;42 x.item = element;43 return oldVal;44 }45}3.4 LinkedList 性能分析
3.4.1 时间复杂度对比
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 头部插入 | O(1) | 直接修改头节点引用 |
| 尾部插入 | O(1) | 直接修改尾节点引用 |
| 中间插入 | O(n) | 需要遍历到指定位置 |
| 头部删除 | O(1) | 直接修改头节点引用 |
| 尾部删除 | O(1) | 直接修改尾节点引用 |
| 中间删除 | O(n) | 需要遍历到指定位置 |
| 随机访问 | O(n) | 需要遍历链表 |
| 查找元素 | O(n) | 需要遍历比较 |
3.4.2 空间复杂度
- 节点开销:每个元素需要额外的节点对象(包含item、next、prev引用)
- 内存分散:元素在内存中分散存储,缓存不友好
- 引用开销:每个节点有两个引用(next、prev)
3.5 LinkedList 使用示例
3.5.1 基本操作示例
1public class LinkedListBasicExample {2 3 public static void main(String[] args) {4 // 创建LinkedList5 LinkedList<String> list = new LinkedList<>();6 7 // 添加元素8 list.add("Java");9 list.addFirst("Python"); // 添加到头部10 list.addLast("C++"); // 添加到尾部11 12 // 获取元素13 String first = list.getFirst();14 String last = list.getLast();15 16 // 删除元素17 list.removeFirst(); // 删除头部元素18 list.removeLast(); // 删除尾部元素19 20 // 遍历21 for (String item : list) {22 System.out.println(item);23 }24 }25}3.5.2 队列和栈操作示例
1public class LinkedListQueueStackExample {2 3 public static void main(String[] args) {4 LinkedList<String> list = new LinkedList<>();5 6 // 作为队列使用7 list.offer("任务1"); // 入队8 list.offer("任务2");9 list.offer("任务3");10 11 while (!list.isEmpty()) {12 String task = list.poll(); // 出队13 System.out.println("处理任务: " + task);14 }15 16 // 作为栈使用17 list.push("操作1"); // 压栈18 list.push("操作2");19 list.push("操作3");20 21 while (!list.isEmpty()) {22 String operation = list.pop(); // 弹栈23 System.out.println("执行操作: " + operation);24 }25 }26}3.5.3 双向遍历示例
1public class LinkedListBidirectionalExample {2 3 public static void main(String[] args) {4 LinkedList<String> list = new LinkedList<>();5 list.addAll(Arrays.asList("A", "B", "C", "D", "E"));6 7 // 正向遍历8 System.out.println("正向遍历:");9 for (String item : list) {10 System.out.print(item + " ");11 }12 13 // 反向遍历14 System.out.println("\n反向遍历:");15 Iterator<String> descendingIterator = list.descendingIterator();16 while (descendingIterator.hasNext()) {17 System.out.print(descendingIterator.next() + " ");18 }19 20 // 使用ListIterator双向遍历21 System.out.println("\nListIterator遍历:");22 ListIterator<String> listIterator = list.listIterator();23 while (listIterator.hasNext()) {24 System.out.print(listIterator.next() + " ");25 }26 27 System.out.println();28 while (listIterator.hasPrevious()) {29 System.out.print(listIterator.previous() + " ");30 }31 }32}4.1 Vector 概述
Vector是最早的List实现,类似ArrayList但线程安全,而Stack继承自Vector并实现LIFO栈功能:
- 🔒 线程安全:所有方法都使用synchronized同步
- 🚧 性能较低:同步机制导致性能开销大
- 📈 动态扩容:默认扩容为原来的两倍
- 📚 栈结构:Stack类提供push/pop等栈操作
- ⚠️ 过时设计:现代应用中一般不推荐使用
Vector与ArrayList对比
| 特性 | Vector | ArrayList |
|---|---|---|
| 线程安全 | 是 | 否 |
| 性能 | 较低 | 较高 |
| 扩容机制 | 默认2倍 | 默认1.5倍 |
| 适用场景 | 多线程环境(不推荐) | 单线程环境 |
Stack使用示例
1public class StackExample {2 public static void main(String[] args) {3 Stack<String> stack = new Stack<>();4 5 // 压栈操作6 stack.push("第一个元素");7 stack.push("第二个元素");8 stack.push("第三个元素");9 10 // 查看栈顶但不移除11 System.out.println("栈顶元素: " + stack.peek());12 13 // 出栈操作14 System.out.println("弹出元素: " + stack.pop());15 System.out.println("弹出后栈顶: " + stack.peek());16 17 // 判断是否为空18 System.out.println("栈是否为空: " + stack.isEmpty());19 20 // 查找元素位置21 System.out.println("第一个元素位置: " + stack.search("第一个元素"));22 }23}- 使用
Collections.synchronizedList(new ArrayList<>())代替 Vector - 使用
ArrayDeque或LinkedList代替 Stack - 需要并发安全时,使用
CopyOnWriteArrayList或ConcurrentLinkedDeque
4. 实际应用场景
4.1 数据处理和分析
1public class DataProcessingExample {2 3 /**4 * 用户行为数据分析5 */6 public static void userBehaviorAnalysis() {7 List<UserAction> actions = new ArrayList<>();8 9 // 收集用户行为数据10 actions.add(new UserAction("用户A", "点击", "2024-01-01 10:00:00"));11 actions.add(new UserAction("用户B", "购买", "2024-01-01 10:05:00"));12 actions.add(new UserAction("用户A", "浏览", "2024-01-01 10:10:00"));13 14 // 按用户分组统计15 Map<String, List<UserAction>> userActions = actions.stream()16 .collect(Collectors.groupingBy(UserAction::getUserId));17 18 // 分析用户行为模式19 userActions.forEach((userId, userActionsList) -> {20 System.out.println("用户 " + userId + " 的行为:");21 userActionsList.forEach(action -> 22 System.out.println(" - " + action.getAction() + " at " + action.getTimestamp())23 );24 });25 }26 27 /**28 * 日志数据过滤和排序29 */30 public static void logDataProcessing() {31 List<LogEntry> logs = new ArrayList<>();32 33 // 模拟日志数据34 logs.add(new LogEntry("ERROR", "数据库连接失败", "2024-01-01 10:00:00"));35 logs.add(new LogEntry("INFO", "用户登录成功", "2024-01-01 10:01:00"));36 logs.add(new LogEntry("WARN", "内存使用率过高", "2024-01-01 10:02:00"));37 38 // 按级别过滤并排序39 List<LogEntry> errorLogs = logs.stream()40 .filter(log -> "ERROR".equals(log.getLevel()))41 .sorted(Comparator.comparing(LogEntry::getTimestamp))42 .collect(Collectors.toList());43 44 System.out.println("错误日志:");45 errorLogs.forEach(log -> 46 System.out.println(log.getTimestamp() + " - " + log.getMessage())47 );48 }49}5051// 辅助类52class UserAction {53 private String userId;54 private String action;55 private String timestamp;56 57 // 构造函数、getter、setter省略58 public UserAction(String userId, String action, String timestamp) {59 this.userId = userId;60 this.action = action;61 this.timestamp = timestamp;62 }63 64 public String getUserId() { return userId; }65 public String getAction() { return action; }66 public String getTimestamp() { return timestamp; }67}6869class LogEntry {70 private String level;71 private String message;72 private String timestamp;73 74 // 构造函数、getter、setter省略75 public LogEntry(String level, String message, String timestamp) {76 this.level = level;77 this.message = message;78 this.timestamp = timestamp;79 }80 81 public String getLevel() { return level; }82 public String getMessage() { return message; }83 public String getTimestamp() { return timestamp; }84}4.2 缓存和配置管理
1public class CacheConfigExample {2 3 /**4 * 配置项缓存管理5 */6 public static void configCacheManagement() {7 List<ConfigItem> configCache = new ArrayList<>();8 9 // 添加配置项10 configCache.add(new ConfigItem("db.url", "jdbc:mysql://localhost:3306/test"));11 configCache.add(new ConfigItem("db.username", "admin"));12 configCache.add(new ConfigItem("cache.ttl", "3600"));13 14 // 配置项查找和更新15 String dbUrl = findConfig(configCache, "db.url");16 System.out.println("数据库URL: " + dbUrl);17 18 // 批量更新配置19 List<ConfigItem> updates = Arrays.asList(20 new ConfigItem("db.url", "jdbc:mysql://localhost:3306/prod"),21 new ConfigItem("cache.ttl", "7200")22 );23 24 updateConfigs(configCache, updates);25 26 // 配置验证27 validateConfigs(configCache);28 }29 30 private static String findConfig(List<ConfigItem> cache, String key) {31 return cache.stream()32 .filter(item -> key.equals(item.getKey()))33 .findFirst()34 .map(ConfigItem::getValue)35 .orElse(null);36 }37 38 private static void updateConfigs(List<ConfigItem> cache, List<ConfigItem> updates) {39 updates.forEach(update -> {40 cache.removeIf(item -> update.getKey().equals(item.getKey()));41 cache.add(update);42 });43 }44 45 private static void validateConfigs(List<ConfigItem> cache) {46 List<String> requiredKeys = Arrays.asList("db.url", "db.username", "cache.ttl");47 List<String> missingKeys = requiredKeys.stream()48 .filter(key -> findConfig(cache, key) == null)49 .collect(Collectors.toList());50 51 if (!missingKeys.isEmpty()) {52 System.out.println("缺少必需配置: " + missingKeys);53 }54 }55}5657class ConfigItem {58 private String key;59 private String value;60 61 public ConfigItem(String key, String value) {62 this.key = key;63 this.value = value;64 }65 66 public String getKey() { return key; }67 public String getValue() { return value; }68}5. 最佳实践总结
5.1 List实现类选择策略
选择合适的List实现类需要考虑以下因素:
- 访问模式:频繁随机访问选择ArrayList,频繁插入删除选择LinkedList
- 内存要求:内存敏感选择ArrayList,内存充足可选择LinkedList
- 线程安全:单线程环境选择ArrayList/LinkedList,多线程环境选择CopyOnWriteArrayList
- 性能要求:对性能要求极高时,需要根据具体场景进行性能测试
| 实现类 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| ArrayList | 频繁随机访问、数据量相对固定 | 随机访问快、内存连续、缓存友好 | 插入删除慢、扩容开销 |
| LinkedList | 频繁插入删除、队列栈操作 | 插入删除快、双向遍历 | 随机访问慢、内存分散 |
| Vector | 需要线程安全(不推荐) | 线程安全 | 性能差、已被替代 |
| CopyOnWriteArrayList | 读多写少、线程安全 | 线程安全、读性能好 | 写性能差、内存开销大 |
5.2 性能优化策略
1public class ListPerformanceOptimization {2 3 /**4 * 预分配容量优化5 */6 public static void capacityOptimization() {7 // 知道大概元素数量时,预分配容量8 int expectedSize = 10000;9 List<String> optimizedList = new ArrayList<>(expectedSize);10 11 // 避免频繁扩容12 for (int i = 0; i < expectedSize; i++) {13 optimizedList.add("item" + i);14 }15 }16 17 /**18 * 批量操作优化19 */20 public static void batchOperationOptimization() {21 List<String> list = new ArrayList<>();22 List<String> items = Arrays.asList("a", "b", "c", "d", "e");23 24 // 批量添加,比循环add效率高25 list.addAll(items);26 27 // 批量删除28 list.removeAll(Arrays.asList("a", "b"));29 30 // 批量替换31 Collections.replaceAll(list, "c", "C");32 }33 34 /**35 * 迭代器优化36 */37 public static void iteratorOptimization() {38 List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));39 40 // 使用迭代器进行安全的删除操作41 Iterator<String> iterator = list.iterator();42 while (iterator.hasNext()) {43 String item = iterator.next();44 if ("b".equals(item)) {45 iterator.remove(); // 安全删除46 }47 }48 49 // 使用ListIterator进行双向遍历和修改50 ListIterator<String> listIterator = list.listIterator();51 while (listIterator.hasNext()) {52 String item = listIterator.next();53 if ("c".equals(item)) {54 listIterator.set("C"); // 安全修改55 }56 }57 }58}5.3 常见陷阱和解决方案
- ConcurrentModificationException:在遍历过程中修改集合会抛出此异常
- Arrays.asList()的不可变性:返回的List不支持add/remove操作
- subList的视图特性:对子列表的修改会影响原列表
- null值处理:某些操作不支持null值,需要特别注意
1public class ListCommonTraps {2 3 /**4 * ConcurrentModificationException示例5 */6 public static void concurrentModificationExample() {7 List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));8 9 // 错误:在遍历过程中删除元素10 try {11 for (String item : list) {12 if ("b".equals(item)) {13 list.remove(item); // 抛出ConcurrentModificationException14 }15 }16 } catch (Exception e) {17 System.out.println("异常: " + e.getMessage());18 }19 20 // 正确:使用迭代器删除21 Iterator<String> iterator = list.iterator();22 while (iterator.hasNext()) {23 String item = iterator.next();24 if ("b".equals(item)) {25 iterator.remove(); // 安全删除26 }27 }28 }29 30 /**31 * Arrays.asList()陷阱32 */33 public static void arraysAsListTrap() {34 List<String> list = Arrays.asList("a", "b", "c");35 36 // 错误:不支持add操作37 try {38 list.add("d"); // 抛出UnsupportedOperationException39 } catch (Exception e) {40 System.out.println("异常: " + e.getMessage());41 }42 43 // 正确:创建新的ArrayList44 List<String> mutableList = new ArrayList<>(Arrays.asList("a", "b", "c"));45 mutableList.add("d"); // 正常添加46 }47 48 /**49 * subList陷阱50 */51 public static void subListTrap() {52 List<String> original = new ArrayList<>(Arrays.asList("a", "b", "c", "d", "e"));53 List<String> subList = original.subList(1, 4); // [b, c, d]54 55 System.out.println("原列表: " + original);56 System.out.println("子列表: " + subList);57 58 // 修改子列表会影响原列表59 subList.set(0, "B");60 System.out.println("修改后原列表: " + original);61 System.out.println("修改后子列表: " + subList);62 }63}5.4 测试和调试建议
1public class ListTestingDebugging {2 3 /**4 * 单元测试示例5 */6 public static void unitTestExample() {7 List<String> list = new ArrayList<>();8 9 // 测试添加操作10 assert list.isEmpty();11 list.add("test");12 assert list.size() == 1;13 assert "test".equals(list.get(0));14 15 // 测试删除操作16 list.remove("test");17 assert list.isEmpty();18 19 System.out.println("所有测试通过");20 }21 22 /**23 * 性能测试示例24 */25 public static void performanceTestExample() {26 int size = 100000;27 28 // ArrayList性能测试29 long start = System.currentTimeMillis();30 List<Integer> arrayList = new ArrayList<>();31 for (int i = 0; i < size; i++) {32 arrayList.add(i);33 }34 long arrayListTime = System.currentTimeMillis() - start;35 36 // LinkedList性能测试37 start = System.currentTimeMillis();38 List<Integer> linkedList = new LinkedList<>();39 for (int i = 0; i < size; i++) {40 linkedList.add(i);41 }42 long linkedListTime = System.currentTimeMillis() - start;43 44 System.out.println("ArrayList添加" + size + "个元素耗时: " + arrayListTime + "ms");45 System.out.println("LinkedList添加" + size + "个元素耗时: " + linkedListTime + "ms");46 }47}6. 总结
List接口作为Java集合框架的核心,提供了有序、可重复、可索引的集合操作能力。通过深入理解ArrayList和LinkedList的实现原理,我们可以根据具体的使用场景选择最合适的实现类,并通过最佳实践来优化性能、避免常见陷阱。
在实际开发中,需要综合考虑以下几个方面:
- 性能要求:根据访问模式选择合适的数据结构
- 内存约束:考虑内存使用和缓存效率
- 线程安全:在多线程环境下选择合适的实现
- 代码可维护性:遵循最佳实践,编写清晰、高效的代码
通过合理使用List集合,我们可以构建出高效、可靠的Java应用程序。
7. 面试题精选
7.1 基础概念题
Q: ArrayList和LinkedList的区别是什么?
A: 主要区别包括:
- 底层实现:ArrayList基于动态数组,LinkedList基于双向链表
- 随机访问:ArrayList O(1),LinkedList O(n)
- 插入删除:ArrayList O(n),LinkedList O(1)(头尾操作)
- 内存布局:ArrayList连续存储,LinkedList分散存储
- 缓存友好性:ArrayList更好,LinkedList较差
Q: ArrayList的扩容机制是怎样的?
A: ArrayList扩容机制:
- 默认初始容量:10
- 扩容触发:当元素个数超过当前容量时
- 扩容倍数:1.5倍(oldCapacity + oldCapacity >> 1)
- 扩容过程:创建新数组,复制原有元素
- 最大容量:Integer.MAX_VALUE - 8
7.2 性能优化题
Q: 如何优化ArrayList的性能?
A: 主要优化策略:
- 预分配容量:使用带初始容量的构造函数
- 批量操作:使用addAll()而非循环add()
- 避免频繁插入删除:在列表中间频繁操作影响性能
- 使用迭代器删除:避免ConcurrentModificationException
- 及时trimToSize():释放多余空间
Q: 什么场景下选择LinkedList?
A: LinkedList适用场景:
- 频繁的插入删除操作
- 需要实现队列或栈功能
- 元素数量变化较大
- 对随机访问性能要求不高
- 需要双向遍历
7.3 实践应用题
Q: 如何实现线程安全的List操作?
A: 线程安全方案:
- Collections.synchronizedList():包装现有List
- CopyOnWriteArrayList:读多写少场景
- Vector:不推荐,性能较差
- 外部同步:使用synchronized或Lock
- 并发集合:考虑使用ConcurrentLinkedQueue等
Q: 如何处理List中的ConcurrentModificationException?
A: 解决方案:
- 使用迭代器删除:
iterator.remove() - 使用removeIf():Java 8+的方法
- 创建副本:
new ArrayList<>(originalList) - 使用CopyOnWriteArrayList:写时复制
- 外部同步:确保单线程访问
- 理解底层实现:掌握ArrayList和LinkedList的内部结构
- 性能分析:能够分析不同操作的时间复杂度
- 场景选择:根据具体需求选择合适的实现类
- 最佳实践:了解常见陷阱和优化策略
- 线程安全:理解并发环境下的使用方案
通过本章的学习,你应该已经掌握了Java List集合的核心概念、实现原理和最佳实践。List是Java开发中最常用的集合类型之一,深入理解其特性和使用场景,对于编写高效、可靠的Java程序至关重要。
参与讨论