极简工具箱

免费在线工具 · 本地计算 · 无需上传

Java 集合怎么选:ArrayList、HashMap 与遍历删除

`for (String s : list) list.remove(s)` 抛异常,改用 `Iterator.remove()` 就好了。集合的坑大多集中在「边遍历边修改」这一件事上。

广告位(百度联盟)

一、先按需求选接口

需求选择
有序、可重复、随机访问ArrayList
频繁头部插入/删除、当队列用ArrayDeque(不是 LinkedList)
去重HashSet / LinkedHashSet(保序)/ TreeSet(排序)
键值查询HashMap / LinkedHashMap / TreeMap
线程安全ConcurrentHashMap、CopyOnWriteArrayList

面向接口编程:变量声明用 List<String> list = new ArrayList<>();,方便替换实现。

二、ArrayList 与 LinkedList

常见误解是「LinkedList 插入删除快」。真实情况:

而且 LinkedList 每个节点有额外对象开销,缓存局部性差。绝大多数场景 ArrayList 更快。需要队列时用 ArrayDeque,它的头尾操作都是 O(1) 且没有额外节点开销。

三、HashMap 的容量与扩容

Map<String, Integer> map = new HashMap<>(16);      // 指定初始容量
Map<String, Integer> map2 = new HashMap<>(16, 0.75f); // 容量 + 负载因子

预设容量的算法是「期望元素数 / 0.75 + 1」,再取最近的 2 的幂。比如要放 100 个元素,初始容量给 128 或 256。

Java 8 之后,单个桶的链表长度超过 8 且容量 ≥ 64 时会转成红黑树,避免哈希碰撞导致 O(n) 查找。

四、遍历时删除元素

// 错误:抛 ConcurrentModificationException
for (String s : list) {
    if (s.startsWith("a")) list.remove(s);
}

原因:for-each 底层用 Iterator,list.remove 修改了 modCount,迭代器下次 next() 检查到不一致就抛异常。

三种正确写法:

// 1. 显式使用 Iterator(推荐,兼容性最好)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().startsWith("a")) it.remove();
}

// 2. removeIf(Java 8+,最简洁)
list.removeIf(s -> s.startsWith("a"));

// 3. 倒序索引删除
for (int i = list.size() - 1; i >= 0; i--) {
    if (list.get(i).startsWith("a")) list.remove(i);
}

Map 同理,用 map.entrySet().iterator() 或 map.entrySet().removeIf(...)。

五、ConcurrentHashMap 不是万能的

它保证单个操作的原子性,但「先检查再写入」的组合操作仍不是原子的:

// 不安全:两个线程可能都通过判断
if (!map.containsKey(k)) map.put(k, init());

// 安全:原子操作
map.computeIfAbsent(k, key -> init());
map.merge(k, 1, Integer::sum);

另外 ConcurrentHashMap 不允许 null 键和 null 值,这与 HashMap 不同,迁移时要注意。

六、几个易错点

七、性能习惯

小结

默认用 ArrayList 和 HashMap;队列用 ArrayDeque;已知容量就预设,避免扩容;遍历删除用 Iterator.remove 或 removeIf;ConcurrentHashMap 的组合操作用 computeIfAbsent。

本文相关工具(本地计算,无需上传)

更多使用教程