还记得我刚入职时,用ArrayList存储所有数据,用HashMap解决所有键值对需求。直到那天,我的一个数据导出功能在几千条数据时就内存溢出了,师傅看着我满屏的new ArrayList<>()和new HashMap<>(),无奈地摇了摇头。今天,我就分享下我用“血泪”换来的集合框架心得。

1. 那个让我加班到深夜的ArrayList

  • ​场景:​​ 我需要从一个巨大的日志文件中读取数据,初步解析后存入一个List,再进行后续处理。
  • 错误示范:
List<String> hugeList = new ArrayList<>(); // 默认容量10
for (String line = logFile.readLine(); line != null; line = logFile.readLine()) {
    hugeList.add(line); // 噩梦开始!每次扩容都要复制数组!
}
  • ​问题:​ArrayList在容量不足时会自动扩容(通常是1.5倍),每次扩容都需要将旧数组的数据复制到新数组。当数据量巨大时,频繁的复制操作极其耗费性能和内存。
  • ​解决方案:​​ 如果能预估大致数量,​一定要指定初始容量​!
// 预估有10万行日志,直接指定一个较大的初始容量
List<String> betterList = new ArrayList<>(100000);

2. HashMap vs ConcurrentHashMap:一次惨痛的并发教训

  • ​场景:​​ 我写了一个简单的缓存,用HashMap来存,自认为没问题。结果在测试环境一跑,偶尔就会出现诡异的数据不一致。
  • 错误示范:
public class MyCache {
    private static final Map<String, Object> cache = new HashMap<>();

    public static void put(String key, Object value) {
        cache.put(key, value);
    }
    // ... get 方法
}
  • ​问题:​HashMap不是线程安全的!多线程环境下同时put,可能导致内部链表成环,甚至CPU 100%。
  • 解决方案:

    • ​如果需要简单的线程安全:​​ 使用ConcurrentHashMap
private static final Map<String, Object> safeCache = new ConcurrentHashMap<>();
  • ​如果只是读多写少,需要保持排序:​​ 可以考虑ConcurrentSkipListMap

总结:

集合用起来简单,但用对很难。

  • ArrayList:查改多,随机访问效率高。记得预估容量。
  • LinkedList:频繁在中间插入/删除时考虑。
  • HashMap:万能,但并发时请用ConcurrentHashMap
  • LinkedHashMap:需要保持插入顺序或访问顺序时(做LRU缓存很棒)。

别再像我一样,等到程序崩溃了才去了解它们。

最后修改:2018 年 10 月 24 日
如果觉得我的文章对你有用,请随意赞赏