吃透集合通关面试|Java 集合 + Stream 生产实战真题全集

基于阿里云、腾讯云、华为云官方技术文档及大厂面试真题整理,涵盖基础概念、List、Set、Map、并发集合、队列、迭代器、性能选型、高频坑点、生产实战全模块,适配初/中/高级Java面试,附带标准答题模板、底层原理、易错点解析。

一、基础概念篇(入门必考)

1. Java集合框架的整体结构是什么?

Java集合框架核心分为**Collection(单元素集合)Map(键值对集合)**两大顶层体系,同时包含工具类、迭代器、并发集合拓展体系,整体架构清晰、职责分明:

Collection(存储单个元素,可迭代)
├── List(有序、可重复、支持索引)
│   ├── ArrayList (动态数组,主流常用)
│   ├── LinkedList (双向链表)
│   └── Vector (线程安全数组,遗留类)
├── Set(无序/有序、不可重复、无索引)
│   ├── HashSet (哈希去重,无序)
│   ├── LinkedHashSet (保留插入顺序)
│   └── TreeSet (自然/自定义排序)
└── Queue/Deque(队列,用于存取、排队、任务调度)
    ├── PriorityQueue (优先级队列)
    ├── ArrayDeque (数组双端队列)
    └── 并发队列(ConcurrentLinkedQueue等)

Map(存储Key-Value键值对,无迭代能力,独立体系)
├── HashMap (哈希映射,无序,主流)
├── LinkedHashMap (保留插入/访问顺序,支持LRU)
├── TreeMap (Key有序,红黑树实现)
├── Hashtable (线程安全,遗留类)
└── ConcurrentHashMap (高并发线程安全Map)

2. Collection 和 Collections 的区别?

对比项 Collection Collections
类型 顶层接口 工具类(全静态方法)
作用 定义List、Set、Queue通用规范,包含集合增删改查、遍历核心方法 提供集合排序、同步、空集合、不可变集合等工具方法
实例化 无法直接实例化,需子类实现 无需实例化,直接调用静态方法
常用示例 List、Set、Queue均继承该接口 Collections.sort()、Collections.synchronizedList()、Collections.emptyList()

3. List、Set、Map 的核心区别?

特性 List Set Map
存储结构 单个元素存储 单个元素存储 Key-Value键值对存储
有序性 严格有序(插入顺序) 无序(LinkedHashSet保留插入序、TreeSet排序序) 无序(LinkedHashMap有序、TreeMap排序序)
元素重复性 允许元素重复 元素绝对不可重复 Key不可重复,Value可重复
查询方式 下标索引精准查询 哈希/比较器匹配查询 通过Key唯一查询Value
Null值支持 允许多个null元素 HashSet/LinkedHashSet允许1个null,TreeSet不允许 HashMap允许1个null Key、多个null Value
遍历方式 普通for、增强for、迭代器 增强for、迭代器 entrySet、keySet、values遍历

4. 为什么 Map 接口不继承 Collection 接口?

核心是设计理念和数据模型不匹配,强行继承会破坏集合框架的规范性:

  1. 数据模型不同:Collection存储的是单个独立元素,而Map存储的是Key-Value键值对二元数据,数据结构本质不同;

  2. 核心方法不兼容:Collection的add()、contains()等方法针对单元素设计,无法适配Map的键值对操作逻辑;

  3. 职责隔离:Map是独立的映射体系,专注键值映射、快速查找,Collection专注单元素批量操作,解耦设计更合理;

  4. 拓展性更强:独立体系可针对性实现哈希、红黑树等映射结构,无需兼容单元素集合的约束。

二、List 接口高频面试题(核心必考)

5. ArrayList 和 LinkedList 的深度区别?

对比维度 ArrayList LinkedList
底层数据结构 动态扩容Object数组 双向循环链表(JDK1.6+)
随机访问 O(1),直接通过下标索引访问 O(n),需从头/尾遍历定位元素
尾部增删 O(1)(无扩容时),扩容O(n) O(1),仅修改首尾指针
中间增删 O(n),需批量移动后续元素 O(n)(耗时在定位),修改指针O(1)
内存占用 较小,仅存储元素,无额外开销,存在数组闲置空间 较大,每个节点存储元素+前驱/后继双指针
线程安全 非线程安全 非线程安全
遍历效率 普通for循环最快,迭代器次之 迭代器遍历快,普通for循环极慢(O(n²))
适用场景 绝大多数业务(查询多、增删少) 频繁中间插入/删除、少量查询场景
面试选型总结:95%的业务场景优先用ArrayList,仅高频中间增删场景选用LinkedList。

6. ArrayList 完整扩容机制(JDK1.8 核心)

ArrayList默认采用懒加载机制,初始化不创建数组,首次添加元素才初始化容量,核心规则如下:

核心参数

  • 初始空数组:DEFAULTCAPACITY_EMPTY_ELEMENTDATA(容量0)

  • 首次扩容容量:10

  • 扩容倍数:1.5倍(oldCapacity + oldCapacity >> 1)

  • 负载因子:1(数组满了才触发扩容)

完整扩容流程

  1. 调用add()方法,先校验数组容量,判断是否需要扩容;

  2. 若为空数组,首次扩容为10;

  3. 非空数组,计算新容量=原容量1.5倍;

  4. 若1.5倍容量仍不足,直接使用所需最小容量;

  5. 通过Arrays.copyOf()复制原数组元素到新数组,完成扩容。

// JDK1.8 核心扩容源码
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 核心:1.5倍扩容
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 1.5倍不足则直接使用最小所需容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 数组拷贝,耗时O(n)
    elementData = Arrays.copyOf(elementData, newCapacity);
}

扩容缺点:扩容伴随数组拷贝,频繁扩容会损耗性能,初始化可预估容量提前指定,减少扩容次数。

7. ArrayList 为什么线程不安全?会出现什么问题?

核心原因:新增元素操作非原子性,无锁保护,多线程并发操作会出现数据覆盖、元素丢失、size错乱问题。

// ArrayList 核心不安全源码
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 校验容量
    elementData[size++] = e;  // 非原子操作:赋值+自增两步
    return true;
}

并发不安全场景

  1. 元素覆盖丢失:两个线程同时获取同一个size下标,先后赋值,后赋值的覆盖先赋值的元素;

  2. size计数错乱:两个线程同时完成赋值,size只自增一次,出现size大于实际元素数量;

  3. 数组越界异常:并发扩容时,多线程同时修改数组容量,触发下标越界。

解决方案:并发场景使用CopyOnWriteArrayList,不使用Vector(性能太差)。

8. Vector 和 ArrayList 的核心区别?

对比项 Vector ArrayList
线程安全 安全,所有方法加synchronized全局锁 非线程安全,无锁
性能 极低,全局锁并发阻塞严重 高性能,无锁开销
扩容倍数 固定2倍扩容 1.5倍扩容,内存利用率更高
诞生版本 JDK1.0(早期遗留类) JDK1.2(全新集合框架)
使用现状 完全不推荐生产使用 通用首选List集合

9. CopyOnWriteArrayList 原理、优缺点及适用场景?

核心原理:写时复制(COW)

读操作完全无锁,直接读取原数组;写操作(增删改)加可重入锁,先复制一份新数组,在新数组上完成修改,修改完成后替换原数组,实现读写分离。

// 核心add源码
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        // 复制新数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        // 替换原数组
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

优点

  • 读写分离,读操作极高并发、无阻塞;

  • 线程安全,适配多线程场景;

  • 遍历不抛并发修改异常(弱一致性)。

缺点

  • 写操作开销极大,每次修改都需复制数组,占用内存、消耗CPU;

  • 数据弱一致性,读操作可能读取到旧数据;

  • 不适合大批量写操作场景。

适用场景读多写少(系统配置、白名单、常量列表、本地缓存)。

三、Set 接口高频面试题

10. HashSet 底层实现原理?

HashSet 底层完全基于 HashMap 实现,是 HashMap 的简化封装:

  • HashSet存储的元素作为HashMap的Key

  • HashMap的Value固定为一个静态常量空对象PRESENT,无实际意义;

public class HashSet<E> {
    // 底层依赖HashMap
    private transient HashMap<E, Object> map;
    // 固定Value常量
    private static final Object PRESENT = new Object();
    
    // 添加元素本质是HashMap put操作
    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }
}

11. HashSet 如何保证元素唯一性(不重复)?

通过hashCode() + equals() 双重校验实现去重,完整判断流程:

  1. 新增元素时,先调用对象的hashCode()计算哈希值,定位数组桶下标;

  2. 若对应桶位置无元素:直接存入,判定为新元素;

  3. 若桶位置已有元素:调用equals()逐个比较元素内容;

  4. equals() == true:判定为重复元素,不存储;

  5. equals() == false:判定为哈希碰撞,追加到链表/红黑树中。

核心考点:重写实体类时,必须同时重写 hashCode() 和 equals(),否则会出现「内容相同但哈希值不同」,导致去重失效。

12. HashSet、LinkedHashSet、TreeSet 三者深度对比?

对比项 HashSet LinkedHashSet TreeSet
底层结构 HashMap(数组+链表+红黑树) LinkedHashMap TreeMap(纯红黑树)
元素顺序 完全无序 保留插入顺序 自然排序/自定义排序
Null值支持 允许1个null 允许1个null 不允许null(无法比较)
查询性能 O(1) 极致高效 O(1) 略低于HashSet O(logn) 排序损耗
去重依据 hashCode+equals hashCode+equals compareTo/compare返回0
适用场景 单纯去重、无需有序 去重+保留插入顺序 去重+自动排序

13. TreeSet 两种定制排序方式?

TreeSet基于红黑树排序,必须指定排序规则,支持自然排序自定义比较器排序两种方式:

方式1:实体类实现 Comparable 接口(自然排序)

public class Person implements Comparable<Person> {
    private Integer age;
    // 重写比较方法,定义排序规则
    @Override
    public int compareTo(Person o) {
        return this.age - o.age; // 升序;反之降序
    }
}

方式2:传入 Comparator 比较器(临时自定义排序,优先级更高)

// Lambda表达式实现降序排序
TreeSet<Person> set = new TreeSet<>((p1, p2) -> p2.getAge() - p1.getAge());

注意:TreeSet判定元素重复的依据是compare返回0,而非equals/hashCode,这是高频易错点。

四、Map 接口核心面试题(重中之重)

14. HashMap JDK1.7与1.8底层结构区别?

JDK版本 底层数据结构 核心优化
JDK1.7 数组 + 单向链表 无红黑树,哈希冲突严重时链表过长,查询退化O(n)
JDK1.8+ 数组 + 单向链表 + 红黑树 长链表转红黑树,查询效率优化为O(logn)
树化/链表还原阈值(高频考点)
  • 链表长度**>8** 且 数组容量≥64:链表转为红黑树;

  • 仅链表长度>8但数组容量<64:优先扩容,不树化;

  • 红黑树节点数**<6**:红黑树退化为链表(平衡性能与内存)。

15. HashMap 完整 put 流程(面试满分答案)

  1. 判断数组是否为空/长度为0,无则执行首次扩容初始化容量为16;

  2. 通过哈希算法计算key的hash值:hash = key == null ? 0 : h ^ h>>>16

  3. 通过位运算计算数组下标:index = (table.length - 1) & hash

  4. 若对应下标桶为空,直接新建节点存入键值对,结束流程;

  5. 若桶不为空,判断桶首节点key与当前key是否一致(hash相同+equals为true),一致则覆盖旧value

  6. 若key不一致,判断桶结构是红黑树还是链表:

    • 红黑树:执行红黑树插入逻辑;

    • 链表:遍历链表查找重复key,有则覆盖,无则尾部插入;

  7. 插入完成后,判断链表长度是否达到树化阈值,满足则转为红黑树;

  8. 最终判断元素数量是否超过阈值(容量*0.75),超阈值则触发扩容。

16. HashMap 哈希函数为什么要高低16位异或?

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

核心设计目的

HashMap数组长度默认是2的n次幂,位运算取模只会用到哈希值的低16位,高位完全不参与计算;若哈希值差异仅在高位,会导致大量key哈希冲突、扎堆存储。

高16位与低16位异或,让高位特征融入低位,让哈希值分布更均匀,极大减少哈希冲突概率,提升查询效率。

17. HashMap 扩容机制及JDK1.8优化点?

核心参数:初始容量16,负载因子0.75,扩容倍数2倍,扩容阈值=容量*0.75

扩容触发条件:集合元素个数size > 扩容阈值threshold

JDK1.8扩容核心优化(高频面试)

JDK1.7扩容需要重新遍历所有元素、重新计算哈希下标,效率极低;

JDK1.8优化:扩容后元素下标只有两种可能:原位置原位置+旧容量,无需重新完整计算哈希,大幅提升扩容效率。

18. HashMap 和 Hashtable 全方位对比?

对比项 HashMap Hashtable
线程安全 非线程安全 线程安全(全局synchronized锁)
Null键值支持 允许1个null Key、多个null Value 不允许null Key/Value,直接抛空指针
初始容量 16(2的幂次) 11(非2的幂次)
扩容规则 2倍扩容 2倍+1扩容
迭代器特性 fail-fast 快速失败 非fail-fast
性能与现状 高性能,生产主流 性能极差,遗留类,彻底淘汰

19. LinkedHashMap 如何实现 LRU 缓存淘汰?

LinkedHashMap 继承 HashMap,额外维护一条双向链表,可实现插入顺序或访问顺序排序,原生支持LRU(最近最少使用)缓存策略。

核心实现方式:开启accessOrder=true(访问顺序模式),重写淘汰策略方法。

// 自定义LRU缓存(固定最大容量,淘汰最久未使用元素)
LinkedHashMap<String, Object> lruCache = new LinkedHashMap<>(16, 0.75f, true) {
    // 重写淘汰规则:超过最大容量则移除最久未使用元素
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
        return size() > 100; // 最大缓存100个元素
    }
};

原理:accessOrder=true时,每次get/put访问元素,都会将当前元素移至链表尾部,链表头部始终是最久未使用的元素,超出容量自动淘汰头部元素。

20. TreeMap 核心特点?

  • 底层基于红黑树实现,天然有序;

  • 排序规则:Key自然排序 或 自定义Comparator排序;

  • 支持范围查询:subMap、headMap、tailMap,适配区间数据场景;

  • 增删查时间复杂度稳定O(logn)

  • 不允许Key为null,Value允许null。

五、并发集合高频面试题(进阶必考)

21. ConcurrentHashMap JDK1.7与1.8实现原理差异?

JDK1.7:分段锁 Segment 机制

  • 将整个Map划分为16个独立Segment分段,每个Segment持有独立锁;

  • 多线程可同时操作不同Segment,提升并发度;

  • 底层:Segment + 数组 + 单向链表,无红黑树;

  • 锁粒度大,并发上限有限。

JDK1.8+:CAS + synchronized 细粒度锁(主流)

  • 彻底取消Segment分段锁,底层结构与HashMap一致:数组+链表+红黑树;

  • 锁粒度大幅细化:仅锁定当前操作的数组桶节点,不同桶可完全并发操作,并发度大幅提升;

  • 无冲突用CAS:桶为空时,通过CAS无锁自旋插入节点,性能极高;

  • 有冲突用synchronized:桶存在元素时,使用synchronized锁定桶首节点,替代笨重的ReentrantLock;

  • 树化并发优化:链表转红黑树、红黑树扩容迁移均支持并发安全操作。

核心总结:JDK1.8通过细粒度桶锁+CAS无锁操作,解决了1.7分段锁并发上限低、锁粒度大的问题,高并发场景性能提升显著。

22. ConcurrentHashMap 为什么放弃 ReentrantLock 改用 synchronized?

  • JDK版本锁优化:JDK1.6之后synchronized进行了大量优化(偏向锁、轻量级锁、自旋锁、锁膨胀),性能基本持平甚至超越ReentrantLock;

  • 更低的开销:synchronized是JVM原生锁,无需创建锁对象,内存开销更小、使用更简洁;

  • 细粒度场景适配:桶节点级别的短时间加锁场景,synchronized轻量级锁优势明显,无多余上下文切换开销;

  • 简化代码架构:减少锁工具类依赖,降低并发逻辑复杂度。

23. ConcurrentHashMap 不支持什么操作?有什么坑?

核心短板:不支持全表原子操作,批量方法非原子性。

  • size()非实时精准:并发读写下,size仅为近似值,无法用于强一致性统计场景;

  • 批量方法非原子:putAll、containsValue、clear等方法无全局锁,并发执行会出现数据错乱;

  • 弱一致性迭代器:迭代过程中可感知部分新增数据,无法保证快照一致性;

  • key/value禁止null:为了并发判空安全,杜绝空指针歧义。

六、Queue/Deque 队列面试题(线程通信/任务调度)

24. ArrayDeque 和 LinkedList 作为队列的区别?

生产规范:队列优先使用 ArrayDeque,不推荐LinkedList。

对比项 ArrayDeque LinkedList
底层结构 可变数组+双指针 双向链表
增删性能 极致高效,无节点对象创建开销 需频繁创建节点对象,GC开销大
内存占用 紧凑无冗余 每个节点存双指针,内存占用高
适用场景 普通队列、栈、双端队列业务 极少使用,仅兼容老业务

25. 阻塞队列和非阻塞队列区别?生产常用队列?

非阻塞队列:ConcurrentLinkedQueue,基于CAS无锁,高并发读写性能极高,适合纯异步无阻塞任务

阻塞队列:BlockingQueue 系列,自带锁与阻塞机制,队列满/空时自动阻塞线程,天然适配生产者消费者模型

生产常用队列

  • ArrayBlockingQueue:有界阻塞队列,固定容量,防止任务堆积OOM;

  • LinkedBlockingQueue:无界/有界阻塞队列,线程池默认队列;

  • SynchronousQueue:无容量队列,一对一传递任务,适合瞬时高并发;

  • PriorityQueue:优先级任务调度队列。

七、Iterator 迭代器与快速失败(高频坑点)

26. 什么是 fail-fast 快速失败?什么是 fail-safe 安全失败?

1)fail-fast(快速失败)

普通集合(ArrayList/HashMap)迭代时,若检测到集合结构被修改(新增/删除),直接抛出 ConcurrentModificationException,防止脏数据。

触发原理:迭代时校验 modCount(结构修改次数),不一致则抛异常。

2)fail-safe(安全失败)

并发集合(CopyOnWriteArrayList)迭代时,基于原数组快照遍历,迭代期间集合修改不影响当前遍历,不会抛异常,存在数据弱一致性。

27. 为什么迭代时不能直接增删元素?如何正确删除?

普通for/增强for遍历中增删,会修改modCount,触发并发修改异常。

正确写法(生产标准)

// 迭代器安全删除
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if ("test".equals(s)) {
        iterator.remove(); // 迭代器自带删除,同步更新modCount,无异常
    }
}

八、Stream流集合处理(生产高频必备+场景题)

Java8 Stream 是生产中集合处理的核心手段,替代繁琐的for循环,代码简洁、高效,适配筛选、分组、排序、去重、聚合、转换等所有集合业务场景,以下为核心考点+真实生产案例。

28. Stream 核心生命周期?

  1. 创建流:集合/数组转流(stream()、parallelStream());

  2. 中间操作(延迟执行):filter、map、sorted、distinct、limit等,返回新流,可链式调用;

  3. 终端操作(触发执行):collect、forEach、count、max、min、anyMatch等,触发计算,关闭流。

核心特点:中间操作懒加载,无终端操作不执行,避免无效计算,提升性能。

29. Stream 常用中间操作&终端操作汇总(生产常用)

操作类型 常用方法 生产用途
中间操作 filter() 条件筛选集合元素
中间操作 map() 元素类型转换、字段提取
中间操作 sorted() 集合排序(单字段/多字段升降序)
中间操作 distinct() 元素去重(重写equals/hashCode生效)
中间操作 limit()/skip() 分页截取集合数据
终端操作 collect() 流转集合、分组、聚合(最核心)
终端操作 anyMatch/allMatch/noneMatch 条件匹配判断,返回布尔值
终端操作 max/min/count 集合最值、数量统计

30. 生产场景题1:集合筛选、字段提取、过滤空值(高频CRUD)

场景描述:查询用户列表,过滤状态正常、非空用户,提取用户ID集合,用于批量查询关联数据。

// 生产标准写法:过滤+去空+字段提取+转List
List<User> userList = userMapper.selectAllUser();
List<Long> validUserIdList = userList.stream()
        .filter(Objects::nonNull) // 过滤null用户对象
        .filter(user -> user.getStatus() == 1) // 筛选正常状态用户
        .map(User::getId) // 提取用户ID
        .collect(Collectors.toList());

31. 生产场景题2:集合分组(订单按用户ID分组,核心业务)

场景描述:查询所有订单数据,根据用户ID分组,得到「用户ID-订单列表」映射,用于批量组装用户订单数据。

// 按用户ID分组,Key=用户ID,Value=对应用户订单集合
List<Order> orderList = orderMapper.selectAllOrder();
Map<Long, List<Order>> userOrderMap = orderList.stream()
        .collect(Collectors.groupingBy(Order::getUserId));

进阶分组:分组后聚合统计(每个用户订单数量、订单总金额)

Map<Long, Integer> userOrderCountMap = orderList.stream()
        .collect(Collectors.groupingBy(
                Order::getUserId,
                Collectors.summingInt(Order::getOrderAmount)
        ));

32. 生产场景题3:集合多字段排序(后台列表分页排序)

场景描述:商品列表排序,优先按价格降序,价格相同按上架时间降序。

List<Goods> goodsList = goodsMapper.selectGoodsList();
List<Goods> sortedGoods = goodsList.stream()
        .sorted(Comparator.comparing(Goods::getPrice, Comparator.reverseOrder())
                .thenComparing(Goods::getCreateTime, Comparator.reverseOrder()))
        .collect(Collectors.toList());

33. 生产场景题4:集合去重(对象字段去重,解决重复数据)

场景描述:用户列表根据手机号去重,保留最新一条数据。

// 基于手机号字段去重
List<User> distinctUserList = userList.stream()
        .collect(Collectors.toMap(
                User::getPhone, 
                Function.identity(), 
                (oldVal, newVal) -> newVal // 重复时保留新数据
        ))
        .values()
        .stream()
        .collect(Collectors.toList());

34. 生产场景题5:Stream并行流使用规范(大坑避坑)

场景:大批量集合数据处理,想要提升处理速度,使用 parallelStream() 并行流。

生产禁忌(高频事故点)

  • 并行流内禁止操作非线程安全变量(ArrayList、普通变量累加会数据错乱);

  • 并行流不适合IO密集型任务,仅适合纯内存计算;

  • 线程池为全局公共线程池,自定义业务线程池不隔离,易互相影响。

正确使用示例:纯数据转换、统计计算

// 安全:纯内存统计,无共享变量修改
long validCount = userList.parallelStream()
        .filter(user -> user.getAge() > 18)
        .count();

九、集合生产选型&高频坑点总结(面试绝杀)

35. 业务集合万能选型公式(生产直接套用)

  • 普通查询多、增删少:首选 ArrayList

  • 频繁中间增删:使用 LinkedList(极少用)

  • 单纯去重无序:HashSet

  • 去重+保留插入顺序:LinkedHashSet

  • 去重+排序:TreeSet

  • 单线程键值存储:HashMap

  • 需要有序键值:LinkedHashMap/TreeMap

  • 并发读多写少:CopyOnWriteArrayList

  • 并发键值存储:ConcurrentHashMap

  • 队列任务调度:ArrayDeque、阻塞队列

36. 生产高频踩坑清单(必记)

  • ArrayList 无初始容量:大数据量循环add,频繁扩容导致性能暴跌,预估容量优先初始化;

  • 重写equals不重写hashCode:HashSet/HashMap去重失效,重复数据堆积;

  • 并发用普通集合:多线程操作ArrayList/HashMap,导致数据丢失、死循环、CPU飙高;

  • foreach循环增删元素:必报并发修改异常,必须用迭代器/Stream过滤;

  • 滥用并行流:IO任务、共享变量修改导致数据错乱、业务bug;

  • HashMap大量null值/哈希冲突:导致链表过长,查询性能退化严重。

十、综合场景面试真题(大厂压轴)

37. 如何解决 HashMap 大量哈希冲突导致的性能问题?

  1. 优化hashCode算法:自定义实体类哈希算法,保证哈希值均匀分布;

  2. 初始化预估容量:根据数据量初始化HashMap容量,减少扩容与冲突;

  3. 避免自定义对象作为Key:若使用必须重写hashCode和equals,保证稳定性;

  4. 业务层预处理:提前去重、规整数据,减少哈希碰撞概率;

  5. 高并发场景替换:并发场景改用ConcurrentHashMap,规避线程问题。

38. 读多写少、写多读少分别用什么集合?生产方案?

  • 读多写少:CopyOnWriteArrayList、ConcurrentHashMap,利用读写分离、无锁读提升并发性能;

  • 写多读少:规避CopyOnWrite(写开销大),使用ConcurrentLinkedQueue、普通ArrayList(单线程写)、ConcurrentHashMap;

  • 极限高并发:结合本地缓存+队列削峰,避免集合频繁读写竞争。

39. Stream处理大数据量集合会OOM吗?如何优化?

会OOM:Stream collect()会一次性加载所有数据到内存,大数据量集合直接流转List会内存溢出。

生产优化方案

  • 大数据量采用分页流式处理,不一次性加载全量数据;

  • 使用迭代器分段遍历处理,替代全量Stream转换;

  • 并行流处理超大集合时,限制并发数,控制内存占用。

40. 为什么生产中禁止使用 Vector、Hashtable?

两者均为古老遗留类,全局synchronized粗粒度锁,并发阻塞严重、性能极差,且扩容机制不合理、API冗余老旧。生产中完全可以被 ConcurrentHashMap、CopyOnWriteArrayList 替代,无任何使用场景。

更多推荐