ArrayList适用于读多写少、随机访问频繁的场景;LinkedList适合头尾高频增删;HashSet用于去重且无需顺序;TreeSet要求有序去重;LinkedHashSet保留插入顺序。

Collection 接口下有哪些常用实现类,各自适用场景是什么
ArrayList、LinkedList、HashSet、TreeSet、LinkedHashSet 是最常被选中的几个。它们不是凭空并列的,选择取决于你到底在操作什么。
-
ArrayList:底层是数组,随机访问快(get(int)是 O(1)),但插入/删除中间元素慢(要移动后续元素);适合读多写少、按索引频繁访问的场景 -
LinkedList:双向链表,头尾增删快(O(1)),但按索引查元素慢(O(n));别只因“链表”二字就用它——除非真在大量做addFirst()或removeLast() -
HashSet:基于HashMap实现,依赖hashCode()和equals(),不保证顺序,插入和查找平均 O(1);去重首选,但对象必须正确重写那两个方法 -
TreeSet:基于红黑树,自动排序(要求元素实现Comparable或传入Comparator),查找/插入是 O(log n);需要有序且去重时才用,别为了“看起来整齐”硬套 -
LinkedHashSet:哈希表 + 链表,保留插入顺序,性能略低于HashSet;调试时看元素添加顺序、或需稳定遍历顺序时有用
常见错误:把 ArrayList 当队列用 remove(0),结果每次都是 O(n);该用 ArrayDeque 的地方没换。
Map 接口的关键实现里,key 为 null 的行为差异
HashMap、LinkedHashMap、ConcurrentHashMap 和 TreeMap 对 null key 的容忍度完全不同,这直接影响你能否安全地存 null。
-
HashMap和LinkedHashMap允许一个nullkey(put(null, "v")合法),get(null)也能返回对应 value -
ConcurrentHashMap禁止nullkey 和nullvalue,否则直接抛NullPointerException;这是线程安全代价之一,不是 bug -
TreeMap不允许nullkey(除非构造时传了Comparator且该比较器能处理null),否则在put()时就报NullPointerException
使用场景提示:如果业务逻辑中 key 可能为 null,又需要并发安全,别硬改 ConcurrentHashMap,考虑用 Optional 包装 key,或者预处理掉 null。
立即学习“Java免费学习笔记(深入)”;
为什么不能直接用 == 比较 Collection 或 Map 中的元素
因为 == 比的是引用地址,而集合框架的设计契约是:判断相等必须用 equals()。几乎所有标准方法(contains()、remove()、containsKey() 等)内部都调用 equals(),不是 ==。
- 常见错误现象:往
HashSet里加了字符串"abc",再用另一个字面量"abc"调contains()返回true(看似正常),但换成自定义对象后始终返回false——大概率是忘了重写equals()和hashCode() - 参数差异:即使两个对象字段值完全一样,只要没重写
equals(),默认继承自Object,仍比较地址 - 性能影响:重写不当会破坏哈希分布,比如
hashCode()总返回固定值,会让HashMap退化成链表,查找从 O(1) 变 O(n)
简单验证方式:打印 obj1.equals(obj2) 和 obj1 == obj2 的结果对比,尤其在单元测试里加这一行。
迭代过程中修改集合导致 ConcurrentModificationException 怎么避开
这个异常不是多线程专属,单线程遍历时用 for-each 或 iterator.next() 并同时调 list.remove() 就会触发。
- 根本原因:集合内部有
modCount计数器,迭代器创建时记录快照值,每次操作集合都会更新它;迭代器每次next()前校验是否匹配,不匹配就抛异常 - 正确做法只有两种:
- 用迭代器自己的
remove()方法(如it.remove()),它会同步更新计数器 - 收集待删元素,遍历完再批量删(
list.removeAll(toRemove))
- 用迭代器自己的
- 别踩的坑:
for (int i = 0; i 循环中删元素,不调整 <code>i会导致跳过下一个元素-
ConcurrentHashMap虽然名字带 concurrent,但它的entrySet().iterator()仍是弱一致的——不抛异常,但可能看不到最新修改,也不保证反映全部当前状态
真正需要高并发修改的场景,优先考虑 CopyOnWriteArrayList(读多写少)或分段加锁策略,而不是靠“绕过异常”来掩盖设计问题。
集合体系的复杂点不在接口多,而在每个实现类都带着明确的时空权衡和契约约束。漏掉一个 hashCode() 重写,或误判了 null key 的支持范围,线上就可能出 silent fail。










