集合使用规范:选对数据结构
程序员的日常:
java
List<String> list = new ArrayList();
// 后面某处发现需要按顺序 → LinkedList
// 又某处发现需要去重 → HashSet
// 最后发现 HashSet 的迭代顺序不稳定 → LinkedHashSet如果一开始就用对了集合,就不用来回重构了。
选择合适的集合
这是一张选择决策表:
| 场景 | 推荐 | 不推荐 | 原因 |
|---|---|---|---|
| 按索引随机访问 | ArrayList | LinkedList | ArrayList O(1),LinkedList O(n) |
| 频繁在中间插入删除 | LinkedList | ArrayList | LinkedList O(1),ArrayList O(n) |
| 需要键值对 | HashMap | ArrayList | HashMap O(1) 查找 |
| 需要自然排序 | TreeMap | HashMap | TreeMap 有序 |
| 需要保持插入顺序 | LinkedHashMap | HashMap | LinkedHashMap 保留顺序 |
| 需要去重 | HashSet | ArrayList+判断 | 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<Integer> stack = new LinkedList<>();
stack.push(1); // 头插 O(1)
stack.pop(); // 头删 O(1)初始化与容量
Diamond 推断类型
java
// ✅ 推荐:用 diamond,操作符让编译器推断泛型
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();
// ❌ 啰嗦:两边都写
List<String> list = new ArrayList<String>();预估容量
java
// ❌ 默认容量 10,扩容 1.5 倍,大数据量时频繁扩容有开销
List<String> list = new ArrayList<>();
// ✅ 知道大概规模,直接指定容量
List<String> list = new ArrayList<>(10000);遍历方式
java
List<String> 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<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if (item.equals("b")) {
it.remove(); // 用 Iterator 的 remove,不是 List 的
}
}判空处理
这是 NPE 的重灾区:
java
List<String> 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<String> 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<String, List<Order>> ordersByUser = new HashMap<>();
// ❌ 每次都检查
List<Order> orders = ordersByUser.get(userId);
if (orders == null) {
orders = new ArrayList<>();
ordersByUser.put(userId, orders);
}
orders.add(order);
// ✅ 一行搞定
ordersByUser.computeIfAbsent(userId, k -> new ArrayList<>()).add(order);Map 遍历
java
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
// 遍历键值对
for (Map.Entry<String, Integer> 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) -> System.out.println(k + "=" + v));总结
选集合的核心就三点:
- 看操作:随机访问多 → ArrayList,中间插入多 → LinkedList,键值对 → Map
- 看顺序:需要排序 → Tree 系列,需要顺序 → Linked 系列
- 看线程:多线程 → ConcurrentHashMap,不要用 Hashtable
没有最好的集合,只有最合适的集合。
