应根据场景选择:已知大小且只读/批量写入用数组,动态增删频繁用预设容量的List,延迟敏感场景优先数组或Span;Java中泛型数组禁止创建,List是唯一安全选择。

直接用 List<t></t> 替代泛型数组,是 Java 和 C# 中应对泛型数组限制的主流做法,但两者在内存布局和访问速度上并不等价——不是简单“换一个类型”就完事,而是存在可测量的差异。
内存布局:连续性 vs 封装层
数组(如 int[])在内存中是纯粹、紧凑、连续的一整块空间,无额外元数据。而 List<t></t> 是一个对象,内部持有一个数组引用,同时包含容量(_capacity)、实际元素数(_size)、对象头等字段。这意味着:
- 相同数量元素下,
List<t></t>比数组多占用 16–24 字节(.NET 对象头 + 字段),且其内部数组本身也存在冗余空间(尤其未预设容量时) - 数组的物理连续性带来更高缓存局部性;
List<t></t>的访问需先解引用对象,再跳转到内部数组,二级指针间接性略影响 CPU 预取效率 - Java 中因类型擦除,
List<string></string>底层数组实际是Object[],存在装箱/拆箱或类型检查开销;C# 的List<int></int>则使用真正的int[],优势更明显
访问速度:O(1) 但有常数级损耗
两者索引读写都是 O(1),但实际耗时不同:
- 数组访问是纯地址计算:
baseAddress + index * sizeof(T),一条 IL 指令(如ldelem.i4)完成 -
List<t></t>的this[i]是属性访问器,需调用get_Item()方法,包含边界检查(index )、方法调用开销、以及一次额外的字段读取(获取内部数组引用) - 实测显示,在百万次随机访问循环中,数组比预设容量的
List<int></int>快约 8%–15%,差距随 CPU 缓存压力增大而更显著
扩容机制带来的隐性成本
这是最关键的差异点,直接影响吞吐与内存稳定性:
- 数组不可扩容,长度固定——适合只读或预分配场景,零运行时不确定性
-
List<t></t>默认扩容策略为翻倍,每次扩容需分配新数组 + 复制旧数据。若初始未指定容量,插入 10 万个元素可能触发 17 次复制,总内存分配量达原始需求的 ~2.9 倍 - 即使预设容量(
new List<int>(100000)</int>),仍比等长数组多出对象头与字段,且无法避免 JIT 对泛型方法的虚调用路径(尽管现代 .NET 已高度优化)
何时该选哪个?看场景本质
不取决于“能不能”,而取决于“稳不稳定、快不快、占不占”:
- 图像像素、音频采样、数学向量、配置表等已知大小+只读/批量写入 → 用数组
- 用户输入流、日志缓冲、消息队列等动态增删频繁 → 用
List<t></t>,但务必预设合理容量 - 对延迟极度敏感(如游戏帧逻辑、高频金融报价)→ 避免
List<t></t>索引访问热点,改用数组+手动管理长度,或考虑Span<t></t>/Memory<t></t> - Java 开发中,泛型数组禁止创建,
List<t></t>是唯一安全选择;但若性能关键,可权衡T[]+@SuppressWarnings("unchecked")(仅限可信上下文)

















