数组索引访问比list快因纯指针偏移无方法调用与边界检查,实测快10%~15%;list.add()扩容需o(n)复制,预设容量可避免;多维数据首选int[,]而非list。

数组快在访问和内存,List强在增删和灵活性——但“快”和“强”都依赖具体用法,不是无条件成立。
数组索引访问为什么比List快一点?
因为 array[i] 是纯指针偏移:基地址 + i × 元素大小,JIT 可直接编译成一条内存读指令;而 list[i] 虽然底层也是数组,但每次调用都要经过实例方法入口、Count 边界检查(Debug 模式下更重)、再解引用 _items[i]。实测 100 万次随机访问,数组平均快 10%~15%,Release 模式下差距收窄,但不为零。
- 值类型数组(如
int[])无装箱开销;List<int></int>同样避免装箱,这点无差异 - 数组连续内存布局对 CPU 缓存更友好,尤其顺序遍历时预取效率高
- 若用
Span<t></t>包装数组或List<t>.AsSpan()</t>,可绕过边界检查,进一步逼近裸指针性能
List.Add() 触发扩容时到底发生了什么?
当 Count == Capacity 时,Add() 会调用 EnsureCapacity(),内部新建一个长度为 oldCapacity * 2 的数组(最小为 4),再用 Array.Copy() 把旧数据逐字节复制过去。这个过程是 O(n),且旧数组暂时无法被 GC 回收,造成瞬时内存翻倍。
- 默认初始容量是 0,第一次
Add()就分配 4 个元素空间;第 5 次触发扩容 → 8;第 9 次 → 16……反复扩容浪费明显 - 写死容量能消除大部分性能差:用
new List<int>(100000)</int>初始化,后续 10 万次Add()几乎全是 O(1) -
Clear()不释放内存,Count归零但Capacity不变;真要释放得赋值list = new List<int>()</int>
传参时用 IReadOnlyList<t></t> 还是 T[]?
优先选 IReadOnlyList<t></t> —— 它既接受 T[] 也接受 List<t></t>,且明确表达“只读”语义,调用方无法误改内容;而用 T[] 作参数会强制传数组,失去灵活性,且数组本身没有只读保护(arr[0] = x 仍合法)。
-
Span<t></t>更优:零分配、栈上切片、支持数组/List/堆内存,但仅限 .NET Core 2.1+ 且不能跨 await 边界 - 避免无谓转换:别在循环里反复调用
list.ToArray()或array.ToList(),这是深拷贝,O(n) 开销白送 - 大数据跨方法传递时,传原始数组或
ReadOnlySpan<t></t>,别包一层List<t></t>增加间接层
多维数据该用 int[,] 还是 List<list>></list>?
用 int[,]。二维数组是单块连续内存,matrix[i, j] 经 JIT 优化后等价于一次地址计算;而 List<list>></list> 是“指针的指针”,每行都是独立堆对象,缓存不友好,内存碎片多,访问慢 3 倍以上,且 GC 压力大。
-
int[][](交错数组)比List<list>></list>稍好(首层是连续指针数组),但仍不如int[,] - 需要动态行列?考虑封装
struct Matrix { private int[] _data; public int Rows, Cols; }手动映射[i,j] → i * Cols + j -
List<t></t>天然不支持多维,强行模拟只会让代码和性能双崩
真正容易被忽略的点是:性能差异往往不出现在单次操作,而藏在组合场景里——比如频繁 Add() 后又反复 ForEach 遍历,此时预设容量 + AsSpan() 能把两段开销都压到最低;而用错类型,可能让本该 O(1) 的访问拖慢整个热路径。










