集合性能优化关键在选对类型和设准初始容量:ArrayList适合随机访问,HashMap适合键值查找,多线程用ConcurrentHashMap;初始容量应按实际数据量预估,避免频繁扩容或内存浪费。

直接说重点:集合类不是“能用就行”,初始容量设错,性能可能差好几倍。核心就两条——选对集合类型,再给它一个“刚刚好”的起步空间。
按场景选对集合类型
别一上来就用 ArrayList 或 HashMap,先看数据怎么用:
- 只读或很少增删、需要按索引快速查?ArrayList 合适;但要是频繁在中间插入/删除,LinkedList 或 CopyOnWriteArrayList 更稳(虽然日常少用)
- 要查 key 快、不关心顺序、key 不重复?HashMap 是首选;如果 key 是字符串且数量固定(比如配置项),EnumMap 或 TreeMap(按自然序)反而更省也更安全
- 只存不查、去重优先?HashSet 底层是 HashMap,插入快;但若元素有天然顺序,TreeSet 虽慢一点,却自带排序,省得后续再 sort
- 多线程写入且不能加锁?ConcurrentHashMap 替代 HashMap,CopyOnWriteArrayList 替代 ArrayList——它们扩容机制不同,但初始容量依然重要
ArrayList 初始容量怎么设才不踩坑
默认 new ArrayList() 看似省事,实际埋雷:Java 8+ 底层数组初始为 0,第一次 add 才扩到 10,之后每次按 1.5 倍增长(10→15→22→33…)。10 万条数据要扩约 17 次,拷贝总量超 60 万次赋值。
- 静态已知数量:比如加载配置表共 128 条,直接 new ArrayList(128)
- 分页上限明确:每页最多 1000 条,最多处理 3 页,设 3000 就够
- 波动但有经验:日志聚合通常 8k–12k 条,保守设 15000
- 真不确定?用 ensureCapacity() 在批量添加前兜底,比如 list.ensureCapacity(5000),比循环里反复调用强得多
- 避免过度预留:设 100 万容量只存 200 个对象,堆内存浪费,GC 压力反而上升
HashMap 初始容量和负载因子要一起调
HashMap 默认容量 16、负载因子 0.75,意味着存到第 12 个元素就触发扩容。扩容要重建整个哈希表,开销远大于 ArrayList 的数组复制。
立即学习“Java免费学习笔记(深入)”;
- 预估元素数 N,初始容量建议设为 ≥ N / 0.75 的最小 2 的幂(JDK 自动向上取整到 2 的幂);比如要存 1000 个键值对,1000 ÷ 0.75 ≈ 1334,最近的 2 的幂是 2048,所以 new HashMap(2048)
- 如果 key 分布极不均匀(比如大量 hash 冲突),可略调高初始容量,或考虑自定义 hash 函数
- 负载因子不是越小越好:设成 0.5 虽减少冲突,但空间利用率腰斩,GC 频率上升;0.75 是时间与空间的经典平衡点
- 构造时传两个参数更稳妥:new HashMap(expectedSize, 0.75f)
批量操作时别忘了 collect 和 ensureCapacity
Stream 或 for 循环批量填充集合,是扩容高发区。光设初始容量还不够,得配合语义清晰的写法:
- 用 Stream.collect 时,优先写成 Collectors.toCollection(() → new ArrayList(N)),语义明确,IDE 也能提示
- 复用同一个 ArrayList(比如循环处理多个批次),每次开始前调一次 ensureCapacity(expectedSize),而不是每次都 new
- addAll() 比循环 add() 更优:它内部会一次性检查并扩容,避免单个 add 触发多次小扩容
- Guava 的 Lists.newArrayListWithCapacity(N) 或 Collections.list()(Java 21+)也是干净的选择,语义比 raw new 更直观


















