Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

  • Java集合

    • Java集合概述
    • ArrayList
      • 1. 核心特点
      • 2. 底层结构
      • 3. 构造方式
      • 4. 添加元素
      • 5. 获取和修改元素
      • 6. 删除元素
      • 7. 扩容机制
      • 8. trimToSize
      • 9. 遍历方式
      • 10. subList 的坑
      • 11. toArray 的正确用法
      • 12. 线程安全
      • 13. ArrayList 与 LinkedList
      • Tips 快问快答
    • LinkedList
    • HashMap
    • LinkedHashMap
    • HashSet
    • TreeMap
    • Queue&Deque
    • 迭代器与遍历机制
    • Collections工具类
    • 集合排序与比较器
    • 集合选型与常见问题
  • Java并发

  • Java IO

  • JVM

目录

ArrayList

ArrayList 是 Java 中最常用的 List 实现,底层基于动态数组。它适合保存有序、可重复、以读取和尾部追加为主的数据。

很多业务代码中的默认列表都应该先考虑 ArrayList,因为它 API 简洁、访问速度快、内存连续性好、CPU 缓存友好。

# 1. 核心特点

特点 说明
有序 按插入顺序保存元素
可重复 允许相同元素出现多次
允许 null 可以存放多个 null
动态扩容 容量不足时创建更大数组并复制
随机访问快 get(index) 是 O(1)
头部和中间插入删除较慢 需要移动元素
非线程安全 多线程写入需要额外同步

示例:

List<String> names = new ArrayList<>();
names.add("Tom");
names.add("Jerry");
names.add("Tom");
System.out.println(names.get(1)); // Jerry

# 2. 底层结构

ArrayList 内部维护一个对象数组和元素个数。

ArrayList
┌────────────────────┐
│ elementData ───┐   │
│ size = 3       │   │
└────────────────┼───┘
                 ▼
Object[]
┌──────┬───────┬──────┬──────┬──────┐
│ Tom  │ Jerry │ Bob  │ null │ null │
└──────┴───────┴──────┴──────┴──────┘
 index   0       1      2      3      4

注意区分:

概念 含义
size 当前元素个数
capacity 底层数组容量
List<String> list = new ArrayList<>(100);
System.out.println(list.size()); // 0

指定初始容量是预留数组空间,不是放入了 100 个元素。

# 3. 构造方式

常见构造方法:

new ArrayList<>();
new ArrayList<>(initialCapacity);
new ArrayList<>(collection);

使用建议:

构造方式 适合场景
new ArrayList<>() 不知道大概元素数量
new ArrayList<>(1000) 能预估元素数量,减少扩容
new ArrayList<>(source) 基于已有集合创建可变副本

如果从不可变集合创建可变列表:

List<String> source = List.of("A", "B");
List<String> mutable = new ArrayList<>(source);
mutable.add("C");

# 4. 添加元素

尾部添加:

list.add("A");

流程:

检查容量
  │
  ├─ 容量足够 -> elementData[size] = e
  │
  └─ 容量不足 -> 扩容 -> 复制旧数组 -> 写入新元素

尾部添加平均是 O(1),但遇到扩容时会退化为 O(n),因为需要复制旧数组。

指定位置添加:

list.add(1, "X");

结构变化:

插入前:
[A, B, C, _, _]

在 index=1 插入 X:
[A, X, B, C, _]
    └── B、C 右移

中间插入需要移动元素,时间复杂度 O(n)。

# 5. 获取和修改元素

随机访问:

String value = list.get(2);

过程:

elementData[index]

数组支持通过下标直接定位,所以是 O(1)。

修改:

list.set(2, "NewValue");

set 不改变集合结构,通常不会触发 fail-fast。

# 6. 删除元素

按索引删除:

String old = list.remove(1);

结构变化:

删除前:
[A, B, C, D, _]

remove(1):
[A, C, D, _, _]
    └── C、D 左移

删除后,尾部多出来的位置会被置为 null,帮助 GC 回收不再使用的对象。

按对象删除:

list.remove("A");

它会从头遍历,删除第一个匹配元素。

注意重载陷阱:

List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3));
numbers.remove(1); // 删除 index=1 的元素,结果 [1, 3]
numbers.remove(Integer.valueOf(1)); // 删除元素 1

# 7. 扩容机制

当元素数量超过底层数组容量时,ArrayList 会扩容。常见 JDK 实现中,新容量大约是旧容量的 1.5 倍。

扩容过程:

旧数组容量不足
  │
  ▼
计算新容量
  │
  ▼
创建新数组
  │
  ▼
复制旧元素
  │
  ▼
elementData 指向新数组

示意:

old elementData
[A, B, C]
   │ 扩容复制
   ▼
new elementData
[A, B, C, _, _]

扩容成本来自数组复制。大量添加元素时,如果能预估数量,应提前指定初始容量:

List<Order> orders = new ArrayList<>(expectedSize);

或者调用:

((ArrayList<Order>) orders).ensureCapacity(expectedSize);

前提是变量真实类型就是 ArrayList。

# 8. trimToSize

trimToSize() 可以把底层数组容量缩小到当前 size。

ArrayList<String> list = new ArrayList<>(10000);
list.add("A");
list.trimToSize();

适合场景:

  • 大批量构建完成后,列表长期保留。
  • 需要减少多余容量占用。

不适合频繁调用,因为它也会复制数组。

# 9. 遍历方式

常见遍历:

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}
for (String value : list) {
    System.out.println(value);
}
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

遍历时删除:

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String value = iterator.next();
    if (value.startsWith("A")) {
        iterator.remove();
    }
}

不要在增强 for 中直接 list.remove(value)。

# 10. subList 的坑

subList 返回的是原列表的视图,不是独立副本。

List<String> list = new ArrayList<>(List.of("A", "B", "C"));
List<String> sub = list.subList(0, 2);

sub.set(0, "X");
System.out.println(list); // [X, B, C]

如果需要独立副本:

List<String> copy = new ArrayList<>(list.subList(0, 2));

原列表结构性修改后,再访问 subList 可能抛出 ConcurrentModificationException。

# 11. toArray 的正确用法

推荐:

String[] array = list.toArray(new String[0]);

或者如果已知大小:

String[] array = list.toArray(new String[list.size()]);

不要直接强转:

String[] array = (String[]) list.toArray(); // ClassCastException

因为无参 toArray() 返回的是 Object[]。

# 12. 线程安全

ArrayList 不是线程安全的。多线程同时写可能导致:

  • 数据覆盖。
  • size 不准确。
  • 读取到未完整写入的数据。
  • 扩容期间状态不一致。

选择:

场景 推荐
单线程或方法内局部变量 ArrayList
多线程读多写少 CopyOnWriteArrayList
简单同步包装 Collections.synchronizedList
高并发生产消费 阻塞队列

同步包装遍历时需要手动同步:

List<String> syncList = Collections.synchronizedList(new ArrayList<>());

synchronized (syncList) {
    for (String value : syncList) {
        System.out.println(value);
    }
}

# 13. ArrayList 与 LinkedList

对比项 ArrayList LinkedList
底层结构 动态数组 双向链表
随机访问 O(1) O(n)
尾部添加 均摊 O(1) O(1)
头部添加 O(n) O(1)
中间插入 O(n) 移动元素 O(n) 定位节点
内存占用 较低 每个节点有前后指针
CPU 缓存友好 好 较差

实际项目中,除非明确需要双端队列语义,否则 ArrayList 通常比 LinkedList 更适合作为普通列表。

# Tips 快问快答

Q:ArrayList 默认容量一定是 10 吗? A:默认构造时通常先是空数组,第一次添加元素时才扩到默认容量。不要把“构造后立即有 10 个容量”理解得过死。

Q:size 和容量有什么区别? A:size 是元素个数,容量是底层数组长度。

Q:ArrayList 查询为什么快? A:底层数组可以通过下标直接定位元素。

Q:为什么中间删除慢? A:删除后面的元素需要整体左移。

Q:remove(1) 删除的是元素 1 还是下标 1? A:对于 List<Integer>,remove(1) 优先匹配按下标删除。

Q:subList 是新集合吗? A:不是独立副本,而是原列表视图。

Q:ArrayList 可以存 null 吗? A:可以,而且可以存多个 null。

Q:ArrayList 线程安全吗? A:不安全,多线程写入应使用并发集合或同步控制。

Q:大量 add 怎么优化? A:预估数量并指定初始容量,减少扩容复制。

Q:为什么普通列表默认选 ArrayList? A:它读写常见场景性能均衡,内存和缓存表现也通常优于链表。

上次更新: 2026/06/24, 16:37:44
Java集合概述
LinkedList

← Java集合概述 LinkedList→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式