ArrayList初始容量应按最常处理的批量规模设定,如日志聚合选512、分页查询选200;HashMap则需用预估最大键数÷0.75并向上取2的幂,如1000键配2048容量。

ArrayList 初始化容量设多少才不浪费又不频繁扩容
Java 中 ArrayList 默认构造容量是 10,但只要 add 超过当前容量,就会触发 Arrays.copyOf(底层调用 System.arraycopy)——这个拷贝成本随数据量指数级上升。预估不准,要么浪费内存,要么反复扩容拖慢吞吐。
关键不是“设多大”,而是“按你最常遇到的批量规模来设”。比如日志聚合通常一次收 500 条,就直接 new ArrayList(512);如果处理分页查询结果,且每页固定 20 条,但最多查 10 页,那就设 200;若完全不可控(如用户上传 CSV 行数未知),至少设 64 或 128 起手,比默认 10 更友好。
- 别用
new ArrayList()无参构造处理批量数据 - 扩容公式是
oldCapacity + (oldCapacity >> 1)(1.5 倍),所以初始值尽量选 2 的幂或接近 1.5 倍链上的数(如 64→96→144→216),减少中间无效分配 - 如果后续要
addAll一个已知大小的集合,优先用new ArrayList(collection),它会直接取collection.size()作初始容量
HashMap 初始容量和 loadFactor 怎么配才不触发 resize
HashMap 扩容代价比 ArrayList 更高:不仅要拷贝数组,还要重 hash、再散列所有 entry。默认 initialCapacity=16、loadFactor=0.75,意味着存到第 13 个元素就 resize —— 对中等规模缓存或临时映射非常危险。
正确做法是:预估「最大可能键数量」÷ 0.75,向上取最近的 2 的幂。例如预计最多存 1000 个 key,1000 ÷ 0.75 ≈ 1334 → 取 2048。写成 new HashMap(2048) 即可,loadFactor 不用动。
立即学习“Java免费学习笔记(深入)”;
- 别传小整数如
new HashMap(100):它会被内部提升为 128,但 100 ÷ 0.75 = 133,实际还是会在 96 个元素后首次扩容 - 如果 key 数极稳定(比如枚举映射),且数量小(EnumMap 比
HashMap零 GC、无 hash、无扩容 -
ConcurrentHashMap同样要预估,但它的扩容是分段的;初始容量建议 ≥ 预期并发线程数 × 2,避免多线程同时触发扩容争抢
Vector 和 Stack 还需要考虑扩容问题吗
需要,而且更糟。Vector 默认扩容是 2 倍(capacityIncrement=0 时),比 ArrayList 的 1.5 倍更激进,容易内存碎片;Stack 是 Vector 子类,同理。它们还带同步开销,纯属历史包袱。
除非维护老代码且明确要求线程安全栈/列表,否则一律替换:Stack → ArrayDeque(无扩容隐患,push/pop 更快);Vector → ArrayList(加显式同步或用 Collections.synchronizedList)。
-
ArrayDeque初始容量设错也不会崩,但它内部数组是 2 的幂,设 64、128、256 等比设 100 更省内存、少边界判断 -
Vector.ensureCapacity虽然存在,但调用时机难把握;不如一开始就给足 —— 可读性差,还掩盖设计问题 - 注意
ArrayDeque不能存null,这是和Stack的行为差异点,上线前得扫空值逻辑
Stream.collect(Collectors.toList()) 会继承上游预估容量吗
不会。Collectors.toList() 返回的是未指定容量的 ArrayList,无论上游是 IntStream.range(0, 10000) 还是 list.subList(0, 500),结果都是默认 10 容量起步,然后疯狂扩容。
必须显式用 Collectors.toCollection:比如 stream.collect(Collectors.toCollection(() -> new ArrayList(1000)))。JDK 16+ 提供了 Collectors.toCollection(ArrayList::new) 的简写,但依然没解决容量问题,本质还是无参构造。
- 如果 stream 来源是已知 size 的
Collection或IntStream,优先转成数组再构造(stream.toArray(String[]::new)),零扩容、零装箱 -
toList()在 JDK 16+ 是不可变 List,底层用私有实现,不走ArrayList,但依然无法预估容量 —— 它是为短链、小数据设计的,别拿它处理万级数据 - 自定义 collector 写起来重,但对高频路径值得:比如固定字段的 DTO 流,可预分配数组 + index 计数,彻底绕过动态扩容
扩容不是黑盒操作,每次 System.arraycopy 都在复制引用、触碰内存页、可能引发 GC。真正省时间的地方,往往藏在 new 那一行的括号里。










