JAVA 集合框架进阶:List 与 Set 的深度解析与实战

Java 集合框架进阶:List 与 Set 的深度解析与实战(2025–2026 视角)

List 和 Set 是日常开发中使用频率最高的两种集合接口,但它们在底层实现、性能特征、使用场景、线程安全、内存占用、遍历方式等方面差异非常大。真正把它们搞明白,能帮你避免 80% 的集合相关 bug 和性能坑。

一、核心区别对比表(面试 + 生产必背)

维度ListSet备注 / 常见误区
是否允许重复元素允许不允许(equals 判定)Set 靠 equals + hashCode 去重
是否有顺序有序(插入顺序 / 索引顺序)大部分无序(HashSet / LinkedHashSet 有序)TreeSet 按 Comparable / Comparator 排序
是否支持索引访问支持(get/set/remove by index)不支持(没有 get(int))这是最本质的区别
允许 null 元素允许(ArrayList / LinkedList 都允许)大部分允许 1 个 null(除 TreeSet)TreeSet / ConcurrentSkipListSet 不允许 null
典型实现类ArrayList、LinkedList、Vector、CopyOnWriteArrayListHashSet、LinkedHashSet、TreeSet、EnumSet、ConcurrentSkipListSet
线程安全实现Collections.synchronizedList / CopyOnWriteArrayListCollections.synchronizedSet / ConcurrentSkipListSet / CopyOnWriteArraySetConcurrentHashMap.newKeySet() 也很常用
内存占用排序LinkedList > ArrayList > VectorHashSet ≈ LinkedHashSet > TreeSetLinkedList 每个节点有 prev/next 指针
随机访问性能ArrayList O(1) / LinkedList O(n)无索引访问
插入/删除性能ArrayList 中间插入 O(n)
LinkedList 任意位置 O(1)(已知位置)
HashSet / LinkedHashSet O(1) 均摊
TreeSet O(log n)
遍历性能ArrayList > LinkedList(缓存局部性)HashSet ≈ LinkedHashSet > TreeSetLinkedList 遍历最慢(指针跳跃)

二、三大主流 List 实现深度对比

实现类底层数据结构随机访问插入/删除(头部)插入/删除(尾部)插入/删除(中间)内存开销典型使用场景
ArrayList动态数组(Object[])O(1)O(n)O(1) 均摊O(n)99% 场景首选、作为默认 List
LinkedList双向链表 + dequeO(n)O(1)O(1)O(n)(需遍历)频繁头尾操作、队列/栈、Deque
Vector动态数组 + synchronizedO(1)O(n)O(1) 均摊O(n)极少用(已过时)
CopyOnWriteArrayList数组 + 写时复制O(1)O(n) 写慢O(n) 写慢O(n) 写慢读多写极少、事件监听器列表

ArrayList 容量增长策略(JDK 8–21 通用)

int newCapacity = oldCapacity + (oldCapacity >> 1);  // 1.5 倍
// 如果还不够,则至少扩到 minCapacity

生产建议:提前知道大致容量 → new ArrayList<>(10000) 避免多次扩容拷贝。

三、Set 的四大主流实现深度对比

实现类底层结构顺序性null 支持线程安全原生支持性能排序(增/查/删)典型使用场景
HashSetHashMap无序允许最快(O(1) 均摊)普通去重、快速判断存在
LinkedHashSetHashMap + 双向链表插入顺序允许略慢于 HashSet需要保持插入顺序的去重集合
TreeSet红黑树(TreeMap)自然顺序 / Comparator不允许O(log n)需要排序、范围查询、first/last/ceiling
ConcurrentSkipListSet跳表(ConcurrentSkipListMap)自然顺序 / Comparator不允许O(log n) 并发安全高并发、有序去重
CopyOnWriteArraySetCopyOnWriteArrayList 包装插入顺序允许是(写时复制)读快写极慢读多写极少的并发场景

TreeSet vs HashSet 选择口诀

  • 要快、不要顺序 → HashSet
  • 要插入顺序 → LinkedHashSet
  • 要排序 / 范围查询 / ceiling / floor → TreeSet
  • 要并发 + 有序 → ConcurrentSkipListSet
  • 要并发 + 插入顺序 + 写很少 → CopyOnWriteArraySet

四、真实业务场景代码模板(直接复制改)

1. 去重 + 保持插入顺序(最常见需求)

// 推荐写法(Java 21+ 更简洁)
Set<String> uniqueOrdered = new LinkedHashSet<>(List.of("a", "b", "a", "c", "b"));

2. Stream 去重(保留首次出现顺序)

List<String> deduped = list.stream()
    .distinct()                     // 内部用 LinkedHashSet 实现,保留顺序
    .toList();

3. TreeSet 范围查询(业务统计常用)

TreeSet<Integer> scores = new TreeSet<>();
// ... 添加分数

// 找出 80~100 分的人数
int count = scores.subSet(80, true, 101, false).size();

// 最高分前三
scores.descendingSet().stream().limit(3).forEach(System.out::println);

4. 高并发去重计数(ConcurrentHashMap 技巧)

// 替代 ConcurrentSkipListSet + 计数
ConcurrentHashMap<String, Boolean> seen = new ConcurrentHashMap<>();
seen.putIfAbsent(key, Boolean.TRUE);  // 返回 null 表示首次出现

5. CopyOnWriteArrayList/Set 典型用法(配置变更监听)

private final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();

public void addListener(Listener l) {
    listeners.add(l);
}

public void fireEvent(Event e) {
    for (Listener l : listeners) {   // 遍历期间可安全修改
        l.onEvent(e);
    }
}

五、2025–2026 生产避坑总结

  1. 永远不要在 for-each 循环中直接 remove(除非用 Iterator.remove)
  2. ArrayList 作为方法返回类型时,考虑 Collections.unmodifiableList / List.copyOf
  3. 频繁头尾操作 → 优先 LinkedList / ArrayDeque 而非 ArrayList
  4. TreeSet / TreeMap 的 Comparator 要保持一致性(equals 与 compareTo 语义一致)
  5. 大集合去重优先 Stream.distinct() 或 LinkedHashSet 构造,而不是反复 contains
  6. 并发场景优先 ConcurrentSkipListSet / CopyOnWrite / ConcurrentHashMap.keySet()
  7. 内存敏感场景:小对象集合用 Trove / fastutil / Eclipse Collections 替代

你当前项目里 List/Set 用得最多的痛点是什么?

  • 大 List 频繁中间插入/删除?
  • 高并发去重?
  • 排序 + 范围查询?
  • Stream 去重顺序不对?
  • 内存占用爆炸?

告诉我具体场景,我可以继续给针对性代码 + 替换方案。

文章已创建 4893

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部