当前位置: 首页 > news >正文

Java集合框架中List常见问题

Java集合框架中List常见基础面试题

考查点:

  • List的基础知识掌握情况

  • 对应实现的区别

  • 线程安全

  • 使用场景

问题:

说下Vector和ArrayList、LinkedList联系和区别?分别的使用场景。

答案:

线程安全
  • ArrayList:底层是数组实现,线程不安全。查询和修改非常快,但增加和删除速度较慢。

  • LinkedList:底层是双向链表,线程不安全。查询和修改速度慢,但增加和删除速度快。

  • Vector:底层是数组实现,线程安全。操作时使用synchronized进行加锁。

使用场景

  • Vector:已经很少使用。

  • 增加和删除场景多时,使用LinkedList

  • 查询和修改多时,使用ArrayList。 

保证线程安全的ArrayList实现方式

在多线程环境中,直接使用ArrayList可能会导致线程安全问题。为了保证线程安全,可以采用以下几种方式:

方式一:自定义同步包装类

可以创建一个自定义的线程安全包装类,根据具体业务需求,在addupdateremove等方法上加锁。这种方式提供了最大的灵活性,但需要手动管理锁,可能会增加编程复杂度。

public class SyncArrayList<E> {private final ArrayList<E> list = new ArrayList<>();private final Object lock = new Object();public void add(E e) {synchronized (lock) {list.add(e);}}public E remove(int index) {synchronized (lock) {return list.remove(index);}}// 其他方法...
}

方式二:使用Collections.synchronizedList

可以使用Collections.synchronizedList方法来包装一个ArrayList,从而实现线程安全。这种方式简单易用,但所有的操作都需要通过集合的迭代器来进行,以保证线程安全。

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

方式三:使用CopyOnWriteArrayList

CopyOnWriteArrayList是一种线程安全的变体,适用于读多写少的场景。每次修改操作都会复制整个底层数组,从而保证读操作的高性能。这种方式简单且线程安全,但可能会因为复制数组而影响性能。

List<String> cowList = new CopyOnWriteArrayList<>();

方式四:使用ReentrantLock

可以使用ReentrantLock来手动管理锁,这种方式提供了比synchronized更细粒度的锁定控制,可以根据具体需求灵活使用。

import java.util.concurrent.locks.ReentrantLock;public class LockArrayList<E> {private final ArrayList<E> list = new ArrayList<>();private final ReentrantLock lock = new ReentrantLock();public void add(E e) {lock.lock();try {list.add(e);} finally {lock.unlock();}}public E remove(int index) {lock.lock();try {return list.remove(index);} finally {lock.unlock();}}// 其他方法...
}

总结

选择哪种方式取决于具体的使用场景和性能需求。如果需要频繁的读写操作,CopyOnWriteArrayList可能是一个不错的选择。如果需要更细粒度的控制,可以考虑使用ReentrantLock。对于简单的线程安全需求,Collections.synchronizedList提供了一个快速且方便的解决方案。

CopyOnWriteArrayList的掌握情况(追问系列)

考查点:

  • CopyOnWriteArrayList的实现原理

  • CopyOnWriteArrayList与Collections.synchronizedList的区别

  • CopyOnWriteArrayList的使用场景

  • CopyOnWriteArrayList的设计思想及其优缺点

问题:

如果回答到上面的点则继续问,没回到则问,了解CopyOnWriteArrayList吗?和Collections.synchronizedList实现线程安全有什么区别,使用场景是怎样的?

答案:

CopyOnWriteArrayList与Collections.synchronizedList的区别
  • CopyOnWriteArrayList

    • 执行修改操作(如addsetremove)时,会复制一份新的数据,代价昂贵。

    • 修改完成后,将原来的集合指向新的集合完成操作。

    • 使用ReentrantLock来保证不会多个线程同时修改。

    • 场景:适合读操作远大于写操作的场景(读操作是不需要加锁的,直接获取,但是删除和增加需要加锁,读多写少)。

  • Collections.synchronizedList

    • 线程安全的原因是因为几乎每个方法都是使用synchronized加同步锁。

    • 场景:写操作性能比CopyOnWriteArrayList好,但是读操作性能并不如CopyOnWriteArrayList。

CopyOnWriteArrayList的设计思想及其优缺点

  • 设计思想

  • 读写分离 + 最终一致

    • 读操作不需要加锁,直接读取数据。

    • 写操作(如添加、删除、修改)时,复制一份新的数据,修改完成后将原集合指向新集合,保证最终一致性。

  • 优点

    • 读操作高性能,不需要加锁。

    • 写操作时通过复制数据,避免了多个线程同时修改数据的问题。

  • 缺点

    • 内存占用问题,由于写时复制,内存里面同时存在两个对象占用的内存,如果对象大则容易发生YoungGC和FullGC。

    • 写操作性能相对较低,因为需要复制数据。

使用场景

  • CopyOnWriteArrayList

    • 适用于读多写少的场景,如缓存、配置信息等。

    • 适用于对并发写入要求不高,但读操作非常频繁的场景。

  • Collections.synchronizedList

    • 适用于写操作频繁,且对读操作性能要求不高的场景。

    • 适用于需要细粒度控制锁的场景。

总结

CopyOnWriteArrayList和Collections.synchronizedList各有优缺点,选择哪种方式取决于具体的使用场景和性能需求。如果读操作远多于写操作,且对内存占用不敏感,可以选择CopyOnWriteArrayList。如果写操作频繁,且对读操作性能要求不高,可以选择Collections.synchronizedList。

ArrayList 扩容机制详解(含 JDK1.7 前后差异 + 核心源码剖析)

一、面试“一句话”总结

当元素个数达到当前容量上限时,ArrayList 会触发一次“1.5 倍扩容”:申请一块 1.5 倍大小的新数组,把旧数据 Arrays.copyOf 过去,再更新内部引用完成扩容。
⚠️ JDK 版本差异:

  • JDK1.7 之前:无参构造器直接给内部数组长度 10。

  • JDK1.7 及以后:无参构造器先给空数组(length=0),第一次 add 时才真正扩容到 10,实现“懒加载”。


二、扩容全过程(JDK1.8 源码)

核心方法链路:add(E)ensureCapacityInternal(size+1)grow(int minCapacity)Arrays.copyOf

// 1. add 入口
public boolean add(E e) {ensureCapacityInternal(size + 1);  // 确保容量足够elementData[size++] = e;return true;
}// 2. 计算所需最小容量
private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 空数组时minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取 10}if (minCapacity - elementData.length > 0)grow(minCapacity);
}// 3. 真正的扩容逻辑
private void grow(int minCapacity) {int oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5 倍if (newCapacity - minCapacity < 0)   // 1.5 倍不够,直接用 minCapacitynewCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)// 防止溢出newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity); // 拷贝
}private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) throw new OutOfMemoryError(); // 溢出变负数return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

三、JDK1.7 前后差异速记表

表格

复制

版本无参构造行为初始容量触发第一次扩容时机
≤1.6直接 new Object[10]10第 11 次 add
≥1.7(含 8+)空数组 Object[0]0第 1 次 add → 扩到 10

四、为什么采用 1.5 倍而不是 2 倍?
  • 折中策略:扩容太频繁会浪费 CPU;一次性扩太大又浪费内存。1.5 倍在大多数场景下能平衡 CPU 与内存开销。

五、面试加分点
  1. 提前避免扩容:若已知大概元素数量,可在构造时指定容量
    new ArrayList<>(expectedSize),减少复制次数与 GC 压力。

  2. 极端情况:当元素数量接近 Integer.MAX_VALUE 时,扩容可能溢出,源码用 hugeCapacity 处理。


一句话收尾
ArrayList 的扩容就是“懒加载 + 1.5 倍复制”,JDK1.7 以后把初始容量延迟到第一次插入,既省内存又保持高性能。

http://www.xdnf.cn/news/1138285.html

相关文章:

  • 【软件开发】主流 AI 编码插件
  • 服务器数据恢复—raid5磁盘阵列崩溃如何恢复数据?
  • 《每日AI-人工智能-编程日报》--2025年7月17日
  • Odoo最佳业务实践:从库存管理重构到全链路协同
  • Jmeter 性能测试响应时间过长怎么办?
  • 下载anaconda和pycharm,管理python环境
  • Kubernetes 学习笔记
  • 暑期自学嵌入式——Day05(C语言阶段)
  • MyBatis 之配置与映射核心要点解析
  • 三轴云台之测距算法篇
  • 硅谷顶级风投发布《2025年AI实战手册》|附下载
  • 【Elasticsearch】Elasticsearch 快照恢复 API 参数详解
  • 一次多架构镜像构建实战:Docker Buildx + Harbor 踩坑记录
  • arping(ARP协议网络测试工具)
  • ota之.加密算法,mcu加密方式
  • git基本操作【GIT-2】
  • 进阶向:智能图像背景移除工具
  • Java并发编程第三篇(深入解析Synchronized)
  • 2025年5大国产ETL工具横向评测
  • 20250717 Ubuntu 挂载远程 Windows 服务器上的硬盘
  • ROS1/Linux——Launch文件使用
  • 创建项目:使用Spring Initializr初始化你的第一个应用程序
  • display:flex弹性布局
  • 聊聊数据和功能测试面临的挑战有哪些?
  • c++ 模板元编程
  • .NET Framework版本信息获取(ASP.NET探针),获取系统的.NET Framework版本
  • React 学习(4)
  • 学习软件测试的第十八天
  • NLP-文本预处理
  • UGUI 性能优化系列:第一篇——基础优化与资源管理