ArrayList尾部add均摊O(1),中间插入需移动元素;LinkedList任意位置add本为O(1),但add(int,E)先遍历查节点致O(n);高频尾加选ArrayList,已知位置插删可用LinkedList配合listIterator()。

为什么 ArrayList 和 LinkedList 的 add() 行为看似一样,性能却差十倍?
因为 ArrayList 底层是数组,add() 默认追加时均摊 O(1),但中间插入(如 add(index, e))需移动后续元素;LinkedList 是双向链表,任意位置 add() 都是 O(1),但前提是已持有目标节点引用——而 add(int, E) 会先调用 node(int) 遍历查找,实际变成 O(n)。
- 高频尾部追加 → 选
ArrayList(注意预设initialCapacity避免多次扩容) - 频繁在头部或已知位置插入/删除 → 用
LinkedList,但必须配合listIterator()或直接操作Node(不推荐手动) - 别用
LinkedList当“更灵活的 List”——它在随机访问、内存占用、缓存局部性上全面劣于ArrayList
HashMap 的 key 为什么必须重写 equals() 和 hashCode()?
因为 HashMap 查找流程是:先算 hashCode() 定位桶(bucket),再用 equals() 在该桶内比对键。若只重写 equals() 不重写 hashCode(),两个逻辑相等的对象可能被散列到不同桶,导致 get() 返回 null;若只重写 hashCode(),所有对象哈希值相同,退化为链表遍历,性能暴跌。
- IDE 自动生成的
hashCode()/equals()覆盖全部参与比较的字段 - 不可变类(如
String、自定义 final 类)作 key 最安全;若用可变对象,确保修改后hashCode()不变 -
Objects.hash(a, b, c)比手写31 * a.hashCode() + b.hashCode()更简洁且防空指针
ConcurrentHashMap 在 JDK 8 后为什么不再用分段锁?
JDK 7 的 ConcurrentHashMap 用 Segment 数组分段加锁,粒度粗、内存开销大;JDK 8 改为 synchronized 锁单个 Node(即哈希桶头结点),配合 CAS + 红黑树优化长链表场景,锁粒度更细、扩容支持并发、代码更简洁。
- 高并发读多写少场景下,
ConcurrentHashMap远优于Collections.synchronizedMap() -
computeIfAbsent()是线程安全的“查无则建”,避免先get()再put()的竞态条件 - 不要假设
size()实时准确——它返回估算值;需精确计数建议用mappingCount()
Stream.collect() 用 Collectors.toList() 为什么有时返回不可变集合?
从 JDK 16 开始,Collectors.toList() 默认返回的是不可修改的 List(内部是 ImmutableCollections.ListN),调用 add() 或 clear() 会抛 UnsupportedOperationException。这不是 bug,而是为了防止意外修改流式处理结果。
立即学习“Java免费学习笔记(深入)”;
- 需要可变列表 → 显式构造:
new ArrayList(stream.collect(Collectors.toList())) - 或改用
Collectors.toCollection(ArrayList::new) - 同理,
toSet()、toMap()在新版本中也倾向返回不可变实现,留意运行时异常
Listmutable = stream .map(String::toUpperCase) .collect(Collectors.toCollection(ArrayList::new));
集合框架的坑不在 API 多难,而在默认行为随 JDK 版本悄悄变化——尤其是不可变性、并发策略和性能边界这些隐性契约。










