跳到正文
Elaine Blog
返回

集合框架的选择与复杂度

Java 基础

集合选择首先是业务语义,其次才是性能。若业务需要去重却使用 List,调用方迟早会在不同位置写出不同的去重规则。

Table of contents

Open Table of contents

用四个问题选择集合

  1. 元素是否有稳定顺序?有顺序优先考虑 List。
  2. 元素是否必须唯一?唯一集合使用 Set。
  3. 是否通过键查找值?使用 Map。
  4. 是否按进入顺序或优先级处理?使用 Queue 或 Deque。

ArrayList 适合按索引读取和尾部追加;HashSet 表达无重复成员;HashMap 表达键值映射;ArrayDeque 适合作为 队列或栈。通常不要用旧的 Vector 和 Stack。

运行订单去重示例

import java.util.*;

public class 订单索引 {
    record 订单(String id, String customerId) {}

    public static void main(String[] args) {
        var orders = List.of(new 订单("O-1", "C-1"), new 订单("O-2", "C-1"));
        var byId = new LinkedHashMap<String, 订单>();
        var customers = new HashSet<String>();

        for (var order : orders) {
            if (byId.putIfAbsent(order.id(), order) != null) {
                throw new IllegalArgumentException("订单号重复: " + order.id());
            }
            customers.add(order.customerId());
        }
        System.out.println(byId.keySet());       // 保持插入顺序
        System.out.println(customers.size());   // 唯一客户数
    }
}

LinkedHashMap 比 HashMap 多维护一条链表,因此能保持插入顺序,也需要额外内存。只有输出顺序属于契约时才支付这项成本。

看懂时间复杂度

时间复杂度描述输入规模增长时,操作次数如何增长。O(1) 表示平均操作次数不随元素数量线性增长;O(n) 表示 可能扫描全部元素。

操作常见集合平均复杂度注意
按索引读取ArrayListO(1)中间插入需移动元素
判断包含HashSetO(1)依赖正确的 equals/hashCode
按键查找HashMapO(1)哈希冲突和扩容有成本
保持排序TreeMapO(log n)比哈希映射更慢但有序
头尾入队ArrayDequeO(1)不允许 null

复杂度不是耗时。缓存局部性、对象分配、数据规模和并发竞争都可能改变实际结果;性能结论需要基准测试。

避免可变键

放入 HashMap 的键如果参与 hashCode() 的字段随后改变,条目可能再也查不到。优先用不可变 record 作为键,并保证 equals() 与 hashCode() 使用同一组字段。

返回集合时明确所有权:List.copyOf(values) 创建不可变快照;Collections.unmodifiableList(values) 只是只读视图,底层 列表变化仍会反映出来。

下一步

阅读泛型、边界与类型擦除,为集合 API 增加编译期类型安全。


分享这篇文章:

上一篇
异常设计与资源关闭
下一篇
泛型、边界与类型擦除