Skip to content

集合使用规范:选对数据结构

程序员的日常:

java
List<String> list = new ArrayList();
// 后面某处发现需要按顺序 → LinkedList
// 又某处发现需要去重 → HashSet
// 最后发现 HashSet 的迭代顺序不稳定 → LinkedHashSet

如果一开始就用对了集合,就不用来回重构了。

选择合适的集合

这是一张选择决策表:

场景推荐不推荐原因
按索引随机访问ArrayListLinkedListArrayList O(1),LinkedList O(n)
频繁在中间插入删除LinkedListArrayListLinkedList O(1),ArrayList O(n)
需要键值对HashMapArrayListHashMap O(1) 查找
需要自然排序TreeMapHashMapTreeMap 有序
需要保持插入顺序LinkedHashMapHashMapLinkedHashMap 保留顺序
需要去重HashSetArrayList+判断HashSet O(1) 去重

ArrayList vs LinkedList:什么时候选谁?

java
// 随机访问为主的场景
List<Integer> ids = new ArrayList<>(); // 90% 的场景用 ArrayList
for (int i = 0; i < ids.size(); i++) {
    process(ids.get(i)); // O(1) 随机访问
}

// LinkedList 的随机访问
for (int i = 0; i < linked.size(); i++) {
    process(linked.get(i)); // O(n),每次 get 都要遍历
}

// LinkedList 只适合这种场景:只从头尾操作
LinkedList&lt;Integer&gt; stack = new LinkedList&lt;&gt;();
stack.push(1);   // 头插 O(1)
stack.pop();      // 头删 O(1)

初始化与容量

Diamond 推断类型

java
// ✅ 推荐:用 diamond,操作符让编译器推断泛型
List&lt;String&gt; list = new ArrayList&lt;&gt;();
Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();

// ❌ 啰嗦:两边都写
List&lt;String&gt; list = new ArrayList&lt;String&gt;();

预估容量

java
// ❌ 默认容量 10,扩容 1.5 倍,大数据量时频繁扩容有开销
List&lt;String&gt; list = new ArrayList&lt;&gt;();

// ✅ 知道大概规模,直接指定容量
List&lt;String&gt; list = new ArrayList&lt;&gt;(10000);

遍历方式

java
List&lt;String&gt; list = Arrays.asList("a", "b", "c");

// 方式1:for-each(大多数场景,推荐)
for (String item : list) {
    System.out.println(item);
}

// 方式2:Lambda(JDK 8+,简单遍历)
list.forEach(System.out::println);

// 方式3:Iterator(需要边遍历边删除时,必须用它)
Iterator&lt;String&gt; it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    if (item.equals("b")) {
        it.remove(); // 用 Iterator 的 remove,不是 List 的
    }
}

判空处理

这是 NPE 的重灾区:

java
List&lt;String&gt; list = null;

// ❌ 直接调用 NPE
if (list.size() > 0) { } // 运行时 NullPointerException

// ❌ 自己写
if (list != null && !list.isEmpty()) { } // 能用,但啰嗦

// ✅ JDK 7+ Objects
if (Objects.nonNull(list) && !list.isEmpty()) { }

// ✅ Apache Commons Lang
if (CollectionUtils.isNotEmpty(list)) { }

// ✅ Spring Framework
if (!CollectionUtils.isEmpty(list)) { }

集合转数组

java
List&lt;String&gt; list = Arrays.asList("a", "b", "c");

// ✅ 推荐:指定数组类型,传空数组让 JVM 决定大小
String[] array = list.toArray(new String[0]);

// ❌ 指定具体大小也可以,但 0 更灵活
String[] array2 = list.toArray(new String[list.size()]);

// ❌ 强制类型转换,容易 ClassCastException
String[] badArray = (String[]) list.toArray();

Map 的一些技巧

computeIfAbsent:懒加载缓存

java
Map&lt;String, List&lt;Order&gt;&gt; ordersByUser = new HashMap&lt;&gt;();

// ❌ 每次都检查
List&lt;Order&gt; orders = ordersByUser.get(userId);
if (orders == null) {
    orders = new ArrayList&lt;&gt;();
    ordersByUser.put(userId, orders);
}
orders.add(order);

// ✅ 一行搞定
ordersByUser.computeIfAbsent(userId, k -&gt; new ArrayList&lt;&gt;()).add(order);

Map 遍历

java
Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.put("a", 1);
map.put("b", 2);

// 遍历键值对
for (Map.Entry&lt;String, Integer&gt; entry : map.entrySet()) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

// 只遍历键
for (String key : map.keySet()) { }

// 只遍历值
for (Integer value : map.values()) { }

// JDK 8+ Lambda
map.forEach((k, v) -&gt; System.out.println(k + "=" + v));

总结

选集合的核心就三点:

  1. 看操作:随机访问多 → ArrayList,中间插入多 → LinkedList,键值对 → Map
  2. 看顺序:需要排序 → Tree 系列,需要顺序 → Linked 系列
  3. 看线程:多线程 → ConcurrentHashMap,不要用 Hashtable

没有最好的集合,只有最合适的集合。

基于 VitePress 构建