跳到正文
Elaine Blog
返回

集合框架的选择与复杂度

Java 基础

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

Table of contents

Open Table of contents

用四个问题选择集合

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

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

运行订单去重示例

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());   // 唯一客户数
    }
}

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

看懂时间复杂度

时间复杂度描述输入规模增长时,操作次数如何增长。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 增加编译期类型安全。


分享这篇文章:

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