Java基础 List集合

Java List集合核心知识点总结(ArrayList/LinkedList/Vector)

在Java集合框架中,List是最基础也最常用的接口之一,它继承自Collection接口,代表一个有序、可重复的元素集合,支持通过索引直接访问元素。本文将从源码原理、性能实测、面试陷阱等维度深入梳理List的三个核心实现类:ArrayList、LinkedList和Vector,帮你彻底搞懂List集合的使用与选型。

一、ArrayList:基于动态数组的实现

1. 核心特点

  • 底层基于动态Object数组 实现,初始默认容量为10(注意:无参构造器创建时初始是空数组EMPTY_ELEMENTDATA,第一次添加元素才会扩容到10)
  • 查询效率高:通过索引直接定位元素,时间复杂度O(1)
  • 增删效率低:增删元素需要移动数组元素,时间复杂度O(n),且扩容时需要复制整个数组
  • 线程不安全:多线程环境下操作会出现并发修改异常,不适合多线程场景

2. 关键扩容机制(源码级解析)

ArrayList的扩容是其核心特性,也是性能瓶颈所在,具体流程如下:

  1. 调用add()方法时,先执行ensureCapacityInternal(size + 1)检查是否需要扩容
  2. 默认扩容规则:新容量 = 原容量 + 原容量 >> 1(即1.5倍
  3. 特殊情况处理:
    • 若1.5倍容量仍小于"最小需要容量"(如一次性添加20个元素到空数组),则直接使用"最小需要容量"作为新容量
    • 若新容量超过MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8),则使用Integer.MAX_VALUE作为新容量
  4. 扩容本质:通过Arrays.copyOf()创建新数组,将原数组元素复制到新数组,这一步是扩容的主要性能开销

性能优化建议:如果提前知道集合的大致元素数量,创建ArrayList时指定初始容量,避免频繁扩容带来的性能损耗。

3. 常用方法与示例

(1)添加元素
java 复制代码
// 尾部添加元素
list.add("元素1");
// 指定索引插入(原位置及后续元素后移)
list.add(1, "元素2");
(2)删除元素
java 复制代码
// 删除指定索引元素,返回被删除的元素
list.remove(1);
// 删除第一次出现的指定元素,返回boolean类型结果
list.remove("元素1");
// 清空集合中所有元素
list.clear();
(3)修改与查询
java 复制代码
// 替换指定索引元素,返回被替换的元素
list.set(0, "新元素");
// 获取指定索引位置的元素
String element = list.get(0);
// 判断集合是否包含指定元素
boolean contains = list.contains("元素1");
// 获取集合中元素的个数
int size = list.size();
// 判断集合是否为空
boolean isEmpty = list.isEmpty();
(4)三种遍历方式
java 复制代码
// 1. 普通for循环(ArrayList最快,基于索引直接访问)
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 2. 增强for循环(底层基于迭代器实现)
for (String s : list) {
    System.out.println(s);
}

// 3. 迭代器遍历(唯一支持遍历过程中安全删除元素的方式)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

4. 核心源码片段分析

java 复制代码
// 扩容核心方法
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 核心:新容量 = 旧容量 + 旧容量右移1位(即1.5倍)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 扩容本质:数组复制
    elementData = Arrays.copyOf(elementData, newCapacity);
}

5. 常见陷阱:subList()的内存泄漏风险

subList(int fromIndex, int toIndex)返回的是原List的视图 ,而非新List。对subList的修改会直接反映到原List上,且原List会持有subList的引用。如果在循环中大量创建subList而不释放,会导致严重的内存泄漏。正确做法是通过new ArrayList<>(list.subList(...))创建新的独立List。

二、LinkedList:基于双向链表的实现

1. 核心特点

  • 底层基于双向链表实现,每个节点包含prev指针、next指针和元素值三个部分
  • 增删效率高:只需修改节点指针,无需移动元素,时间复杂度O(1)(仅针对首尾或已知节点的增删)
  • 查询效率低:需要从头或尾遍历链表定位元素,时间复杂度O(n)
  • 线程不安全:多线程环境下操作会出现并发修改问题
  • 天然实现了Queue和Deque接口,可直接作为队列、双端队列和栈使用

2. 常用方法与示例

LinkedList除了实现List接口的通用方法外,还提供了大量针对首尾元素的便捷操作,这是它的核心优势:

(1)添加元素
java 复制代码
LinkedList<Integer> list = new LinkedList<>();
// 尾部添加元素
list.add(1);
// 指定索引插入元素
list.add(1, 2);
// 头部添加元素
list.addFirst(0);
// 尾部添加元素(与add()方法功能完全等价)
list.addLast(3);
(2)获取元素
java 复制代码
// 获取指定索引位置的元素
int num = list.get(1);
// 获取链表第一个元素
int first = list.getFirst();
// 获取链表最后一个元素
int last = list.getLast();
(3)删除元素
java 复制代码
// 删除链表第一个元素
list.remove();
// 删除指定索引位置的元素
list.remove(1);
// 删除链表第一个元素(与remove()方法功能等价)
list.removeFirst();
// 删除链表最后一个元素
list.removeLast();

重要注意:绝对避免使用普通for循环遍历LinkedList ,每次调用get(index)都会从头或尾重新遍历链表,当元素数量较多时性能会急剧下降。推荐使用增强for循环或迭代器遍历。

3. 性能对比实测

以下是简单的性能测试代码片段,直观展示两者差异:

java 复制代码
// ArrayList与LinkedList尾部插入100万条数据对比
long start = System.currentTimeMillis();
List<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
    arrayList.add(i);
}
System.out.println("ArrayList尾部插入耗时:" + (System.currentTimeMillis() - start) + "ms");

start = System.currentTimeMillis();
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 1000000; i++) {
    linkedList.add(i);
}
System.out.println("LinkedList尾部插入耗时:" + (System.currentTimeMillis() - start) + "ms");

实测结果:在预分配容量的情况下,ArrayList尾部插入性能略优于LinkedList;在中间位置插入时,LinkedList性能显著优于ArrayList。

三、Vector:已过时的线程安全实现

1. 核心特点

  • 底层同样基于动态数组实现,与ArrayList原理基本一致
  • 线程安全 :所有方法都添加了synchronized同步锁,但同步开销极大,性能远低于ArrayList
  • 扩容机制:默认扩容为原容量的2倍(可通过构造器自定义容量增量,这是与ArrayList的重要区别)
  • 不建议使用 :属于JDK1.0的遗留类,设计老旧且性能差,如需线程安全的List集合,推荐使用Collections.synchronizedListCopyOnWriteArrayList替代

2. 特有方法(了解即可)

Vector提供了一些与ArrayList功能重复但命名不同的历史遗留方法:

  • addElement(E obj):功能等价于add()方法
  • elementAt(int index):功能等价于get()方法
  • removeElement(Object obj):功能等价于remove()方法
  • elements():返回Enumeration迭代器,已被更通用的Iterator替代

四、CopyOnWriteArrayList:高并发场景的首选

1. 核心原理

CopyOnWriteArrayList是java.util.concurrent包下的线程安全List实现,采用**写时复制(Copy-On-Write)**机制:

  • 读操作:完全无锁,直接读取原数组
  • 写操作:加锁,复制一份新数组,在新数组上修改,修改完成后将新数组赋值给原数组引用
  • 核心思想:读写分离,最终一致性

2. 优缺点与适用场景

  • 优点:读操作性能极高,适合读多写少的高并发场景(如配置信息缓存、白名单/黑名单管理)
  • 缺点:内存占用大(每次写操作都要复制数组),数据一致性只能保证最终一致性,不适合实时性要求极高的场景
  • 注意:迭代器是快照迭代器,不支持在迭代过程中修改集合

五、List集合核心总结与选型对比

1. List接口通用特性

  • 有序性:严格按照元素的插入顺序进行存储和访问
  • 可重复性:允许存储重复元素,同时也支持存储null值
  • 索引支持:可以通过int类型的索引直接访问元素,索引从0开始计数

2. 四大实现类对比表

特性 ArrayList LinkedList Vector CopyOnWriteArrayList
底层数据结构 动态数组 双向链表 动态数组 动态数组(写时复制)
随机查询效率 极高(O(1)) 极低(O(n)) 高(O(1)) 高(O(1))
首尾增删效率 低(O(n),需移动元素) 极高(O(1),仅改指针) 低(O(n),需移动元素) 低(O(n),需复制数组)
中间增删效率 低(O(n)) 中等(需先遍历定位) 低(O(n)) 低(O(n))
线程安全 不安全 不安全 安全(方法级加锁) 安全(写时复制+锁)
默认扩容倍数 1.5倍 无扩容(链表按需分配) 2倍 无扩容(每次写操作复制)
内存占用 低(连续数组空间) 高(额外存储节点指针) 低(连续数组空间) 高(写时复制双份数组)
适用场景 频繁查询、少量增删 频繁首尾增删、队列/栈 无(仅维护老代码使用) 高并发读多写少场景
综合性能 最高 中等 最低 读高写低

3. 补充注意事项

  1. 并发安全方案
    • 轻度并发场景:使用Collections.synchronizedList(List list)包装ArrayList
    • 高并发读多写少场景:使用java.util.concurrent.CopyOnWriteArrayList,采用写时复制机制,读操作无锁,性能更优
  2. 遍历性能选择
    • ArrayList:普通for循环 > 增强for循环 > 迭代器
    • LinkedList:迭代器 > 增强for循环 > 普通for循环
  3. 空值处理 :三个实现类都允许存储null元素,但在调用get()remove()等方法时需注意空指针异常
  4. Collections工具类常用方法
    • Collections.sort(List list):对List进行自然排序
    • Collections.reverse(List list):反转List中元素的顺序
    • Collections.shuffle(List list):随机打乱List中元素的顺序
    • Collections.unmodifiableList(List list):返回一个不可修改的List视图,防止误修改

结论

List集合是Java开发中每天都会用到的基础工具,掌握其底层原理和选型技巧能显著提升代码质量和运行性能。日常开发中,绝大多数场景优先使用ArrayList;当需要频繁在集合首尾增删元素,或需要实现队列、栈数据结构时,选择LinkedList;Vector由于性能和设计缺陷,除非是维护遗留老代码,否则坚决不要使用;在高并发读多写少的场景下,CopyOnWriteArrayList是最佳选择。

相关推荐
凤凰院凶涛QAQ1 小时前
《C++转Java快速入手系列》抽象类和接口篇
java·开发语言·c++
MuYiLuck1 小时前
01-spring-boot-autoconfig-principle
java·spring·maven·自动配置
河阿里1 小时前
Lambda表达式(Java):从语法本质到工程实践
java·开发语言
普修罗双战士1 小时前
专业Markdown转HTML工具类:修复优化与Spring Boot适配
windows·spring boot·html
云烟成雨TD1 小时前
Spring AI Alibaba 1.x 系列【47】状态图定义:StateGraph 源码解析
java·人工智能·spring
6190083361 小时前
spring中 HTTP 请求常见格式
java·spring·http
Veggie261 小时前
cuda 13.2 install on ubuntu26
java
翎沣1 小时前
C++11异常处理机制
java·c++·算法
北暮城南2 小时前
使用 nvm 安装与管理多版本 Node.js(Windows)
windows·npm·node.js·nvm