为什么List占用的内存几乎是同容量数组的3倍?
内存开销差异的原因
- List
默认的动态扩容机制带来的预留空间浪费
List内部基于数组实现,默认无容量参数初始化时初始容量为4,每次元素数超过当前容量就会将内部数组扩容为原来的2倍。写入2000万条float时,扩容过程最后一步会将容量从16777216(224)翻倍到33554432(225),此时内部数组实际占用128MB(33554432*4字节),但仅使用了前2000万条对应的76MB空间,剩下的空间属于预留未使用的冗余空间。 - 未回收的旧扩容数组带来的临时内存占用
每次扩容都会生成新的更大的数组,拷贝完旧数组元素后,旧数组需要等待GC回收。如果内存统计时GC还未执行,最大的旧数组(16777216容量,对应64MB)还会驻留在内存中,叠加后就会出现观测到的206MB左右的总占用。 - 补充:List
本身的对象开销可忽略,只有对象头、容量、元素数、内部数组引用几个字段,总共仅几十字节,不是内存开销的主要来源。
低开销优化方案
- 方案1:预分配容量(最优,适合提前知道元素总数的场景)
初始化List时直接指定总容量,完全避免扩容过程和预留空间浪费,内存占用和纯数组几乎一致。
代码示例:
// 初始化时直接指定2000万的容量 List<float> listValues = new List<float>(20000000); for (int i = 0; i < 20000000; i++) listValues.Add(i);
- 方案2:写入完成后裁剪多余空间(适合提前不知道元素总数的场景)
所有元素写入完成后调用TrimExcess()方法,会将内部数组的容量调整为和实际元素数一致,释放多余的预留空间。注意如果后续还要新增元素,会触发重新扩容,适合一次性写入的场景。
代码示例:
List<float> listValues = new(); for (int i = 0; i < 20000000; i++) listValues.Add(i); // 裁剪多余容量 listValues.TrimExcess();
- 方案3:直接使用数组(适合完全不需要动态增减元素的场景)
如果场景不需要后续新增、删除元素,直接使用float数组是内存效率最高的选择,没有任何额外封装开销。 - 方案4:使用数组池(适合高频重复创建大集合的场景)
如果需要频繁创建和销毁大的数值集合,可以用ArrayPool<float>租用数组,用完归还,避免频繁大对象GC带来的性能损耗。
内容的提问来源于stack exchange,提问作者Zelos
相关产品推荐
相关产品推荐

