还记得我刚入职时,用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缓存很棒)。
别再像我一样,等到程序崩溃了才去了解它们。