JAVA重点基础、进阶知识及易错点总结(8)List 接口(ArrayList、LinkedList、Vector)


🚀 Java 巩固进阶 · 第8天

主题:List 接口深度解析 ------ SpringBoot 数据处理的基石

📅 进度概览 :掌握 Java 中最常用的集合类型。
💡 核心价值

  • 数据承载 :Controller 返回给前端的 JSON 数组、MyBatis 查询结果集,90% 都是 List
  • 性能抉择 :理解 ArrayList vs LinkedList 的底层差异,避免在大数据量场景选错容器导致系统卡顿。
  • 并发安全 :识别多线程下的 ConcurrentModificationException,掌握线程安全的 List 方案。
  • 现代编程:结合 Stream API 高效处理 List 数据(过滤、转换、聚合)。

一、List 接口核心特性:有序与索引

List 是 Collection 的子接口,核心特征是 「有序」(插入顺序)、可重复、基于索引

1. 核心方法速查(索引操作)

方法签名 作用 时间复杂度 (ArrayList) 备注
void add(int index, E element) 指定位置插入 O(n) 后续元素需移位
E get(int index) 获取指定元素 O(1) 随机访问极快
E remove(int index) 删除指定位置元素 O(n) 后续元素需前移
E set(int index, E element) 修改指定位置元素 O(1) 直接覆盖
int indexOf(Object o) 查找元素首次出现位置 O(n) 需遍历比较
List<E> subList(int from, int to) 截取子列表 O(1) 返回视图,修改会影响原列表

2. 基础代码示例

java 复制代码
List<String> list = new ArrayList<>();
list.add("A"); // index 0
list.add("B"); // index 1
list.add("C"); // index 2

// 1. 指定插入 (移动元素)
list.add(1, "X"); // [A, X, B, C]

// 2. 随机访问 (极快)
String val = list.get(0); 

// 3. 修改 (直接覆盖)
String old = list.set(1, "Y"); // [A, Y, B, C], old="X"

// 4. 删除 (移动元素)
list.remove(2); // [A, Y, C], 删除了"B"

// ⚠️ 注意:subList 返回的是视图,非新集合
List<String> sub = list.subList(0, 2); 
sub.add("Z"); // 原 list 也会变成 [A, Y, Z, C]!

二、ArrayList:绝对的主力军

1. 底层原理揭秘

  • 数据结构 :动态 Object 数组 (Object[] elementData)。
  • 初始容量:默认 10(懒加载,第一次添加时创建)。
  • 扩容机制
    • 当容量不足时,创建新数组,容量为原来的 1.5 倍 (old + old/2)。
    • 代价 :需要调用 System.arraycopy 复制所有元素,频繁扩容会严重损耗性能
    • 优化 :若预估数据量,务必使用构造器指定初始容量 new ArrayList<>(expectedSize)
  • 线程安全不安全 。多线程并发修改会抛出 ConcurrentModificationException 或导致数据丢失。

2. 性能画像

操作 复杂度 原因 适用场景
随机访问 (get/set) O(1) 数组内存连续,通过偏移量直接计算地址 读多写少,频繁根据索引查询
尾部添加 (add) O(1) 直接放入空位 (均摊) 日志收集、结果集组装
中间/头部增删 O(n) 需移动大量元素 (arraycopy) 避免在大数据量头部操作

3. 🏆 SpringBoot 应用场景

  • API 响应public List<UserDTO> getUserList()
  • MyBatis 结果List<Order> orders = orderMapper.selectAll(); (默认返回 ArrayList)
  • 配置列表@ConfigurationProperties 注入的 List 属性。
  • Redis 缓存:存储序列化后的 List 对象。

三、LinkedList:被误解的链表

1. 底层原理揭秘

  • 数据结构 :双向链表 (Node: prev, item, next)。
  • 内存特点 :节点分散在堆内存中,不连续,缓存命中率低(CPU 预取失效)。
  • 无扩容:动态分配节点,无容量限制。

2. 性能真相(重要!)

操作 复杂度 真相解析
首尾增删 O(1) 直接操作 first/last 指针,极快
随机访问 (get) O(n) 必须从头/尾遍历节点,极慢
中间增删 O(n) 误区警示 :虽然删除节点本身是 O(1),但找到该节点需要 O(n) !总耗时 = 查找 + 删除 = O(n)。且在大数据量下,由于缓存不友好,实际表现往往不如 ArrayList

3. 特有方法(双端队列能力)

LinkedList 同时实现了 Deque 接口,可用作队列

java 复制代码
LinkedList<String> queue = new LinkedList<>();
queue.offerFirst("Head"); // 入队头
queue.offerLast("Tail");  // 入队尾
queue.pollFirst();        // 出队头
queue.peekLast();         // 查看队尾

4. ✅ 适用场景

  • 频繁的首尾操作:实现 LIFO 栈或 FIFO 队列。
  • 无需随机访问:只通过 Iterator 遍历的场景。
  • 大数据量头部插入 :如实时日志缓冲(但通常推荐用 ArrayDequeBlockingQueue)。

四、Vector 与 线程安全方案

1. Vector:历史的尘埃

  • 原理 :同步的数组(方法加 synchronized)。
  • 缺点 :全表锁,并发度极低,性能差。❌ 严禁在新项目中使用

2. SpringBoot 中的线程安全 List 方案

若需在多线程环境下使用 List,请选择以下方案:

方案 实现方式 特点 适用场景
CopyOnWriteArrayList new CopyOnWriteArrayList<>() 写时复制。读无锁极快,写时拷贝新数组。 读多写极少 (如监听器列表、配置列表)
Synchronized List Collections.synchronizedList(new ArrayList<>()) 包装器,所有方法加锁。 写操作较多,且需要强一致性
ConcurrentLinkedQueue new ConcurrentLinkedQueue<>() CAS 算法实现的无锁队列。 高并发队列场景 (替代 LinkedList)
java 复制代码
// ✅ 推荐:读多写少的安全列表
List<String> safeList = new CopyOnWriteArrayList<>();
safeList.add("A"); // 写操作会拷贝数组,开销大
String val = safeList.get(0); // 读操作无锁,极快

五、性能对比实战与避坑指南

1. 性能测试结论(10万数据)

操作 ArrayList LinkedList 赢家 原因
尾部添加 ⚡ 极快 ⚡ 快 平手 差别微乎其微
随机访问 (get) O(1) 🐢 O(n) (慢千倍) ArrayList 链表需遍历
中间删除 🐢 O(n) (移位) 🐢 O(n) (查找+删除) ArrayList 数组内存连续,CPU 缓存友好;链表指针跳转慢
头部插入 🐢 O(n) ⚡ O(1) LinkedList 数组需移动所有元素

💡 黄金法则 :除非你需要频繁在头部 插入/删除,或者明确需要队列/栈 结构,否则无脑选 ArrayList

2. ⚠️ 常见避坑:遍历时删除元素

错误写法 :直接在 foreach 循环中 remove,会抛 ConcurrentModificationException

java 复制代码
// ❌ 报错!
for (String s : list) {
    if ("error".equals(s)) {
        list.remove(s); 
    }
}

✅ 正确写法 1:使用 Iterator

java 复制代码
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if ("error".equals(it.next())) {
        it.remove(); // 安全删除
    }
}

✅ 正确写法 2:Java 8 removeIf (推荐)

java 复制代码
list.removeIf(s -> "error".equals(s));

✅ 正确写法 3:倒序遍历 (仅限 ArrayList)

java 复制代码
for (int i = list.size() - 1; i >= 0; i--) {
    if ("error".equals(list.get(i))) {
        list.remove(i);
    }
}

六、🎯 今日实战任务:Stream API 升级版购物车

背景 :实现一个高性能购物车,不仅要用 List 存储,还要用 Stream API 进行数据处理。

需求步骤

  1. 定义 Goods
    • 属性:id (Long), name (String), price (BigDecimal), count (Integer)。
    • 实现 toString, equals, hashCode
  2. 购物车管理类 CartService
    • 内部使用 private final List<Goods> items = new ArrayList<>(); (思考:为什么不用 LinkedList?)。
    • 功能方法
      1. addItem(Goods goods):若商品已存在,增加数量;否则新增。注意线程安全(假设单线程或外部同步)。
      2. removeItem(Long id):根据 ID 删除商品(使用 removeIf)。
      3. updateCount(Long id, int count):修改数量,若 count<=0 则删除。
      4. calculateTotalPrice()核心! 使用 Stream API 计算总价 (mapToDouble -> sum)。
        • 公式:∑(price×count)\sum (price \times count)∑(price×count)
      5. getListByMinPrice(BigDecimal minPrice):使用 Stream filter 筛选价格高于指定值的商品,返回新 List。
  3. 测试类
    • 添加 5 种商品,修改其中一种数量。
    • 删除一种商品。
    • 打印总价(保留 2 位小数)。
    • 筛选价格大于 50 元的商品并打印。
    • 挑战 :尝试在遍历 items 时直接 remove,观察异常,然后改为正确写法。

💡 Stream API 提示

java 复制代码
import java.math.BigDecimal;

// 计算总价
public BigDecimal calculateTotalPrice() {
    return items.stream()
        .map(good -> good.getPrice().multiply(BigDecimal.valueOf(good.getCount())))
        .reduce(BigDecimal.ZERO, BigDecimal::add);
}

// 筛选
public List<Goods> getListByMinPrice(BigDecimal minPrice) {
    return items.stream()
        .filter(good -> good.getPrice().compareTo(minPrice) > 0)
        .collect(Collectors.toList()); // 或 toList() (Java 16+)
}

📝 第8天 · 核心总结

  1. List 选型铁律

    • 默认首选 ArrayList:随机访问快,CPU 缓存友好,适合 95% 的业务场景(查询、遍历、尾部追加)。
    • 仅特定场景选 LinkedList :需要频繁首尾增删,或作为栈/队列使用。
    • 拒绝 Vector:已过时,性能差。
  2. 并发安全

    • ArrayList 非线程安全。
    • 读多写少 → CopyOnWriteArrayList
    • 高并发队列 → ConcurrentLinkedQueue / BlockingQueue
  3. 操作避坑

    • 禁止 在 foreach 循环中直接 remove
    • 使用 Iterator.remove()list.removeIf()
    • 初始化时尽量指定容量,避免频繁扩容拷贝。
  4. 现代 Java 风格

    • 处理 List 数据(过滤、统计、转换)优先使用 Stream API,代码更简洁、函数式。
    • 金额计算务必使用 BigDecimal,严禁使用 double

相关推荐
第二层皮-合肥2 小时前
基于C#的工业测控软件-依赖库
java·开发语言
橘子132 小时前
C++11 lambda表达式
开发语言·c++
2401_857918292 小时前
分布式系统安全通信
开发语言·c++·算法
C^h2 小时前
RTthread消息队列学习
开发语言·算法·嵌入式
openallzzz2 小时前
【面经分享】Java实习
java·开发语言
indexsunny2 小时前
互联网大厂Java面试:从Spring Boot到微服务的逐步挑战
java·数据库·spring boot·redis·微服务·面试·电商
27669582922 小时前
租车帮(悟空)订单查询算法分析
java·服务器·前端·悟空·悟空app·租车帮·租车帮逆向
鬼蛟2 小时前
Spring Boot
java·开发语言
带鱼吃猫2 小时前
C++11 核心特性解析(一):从初始化列表到移动语义,解锁高效对象构造
开发语言·c++