基础概念:内存区域与数据类型
堆(Heap) vs 栈(Stack)
特性 | 栈 | 堆 |
---|---|---|
分配速度 | 极快(移动指针) | 较慢(查找+分配) |
生命周期 | 作用域结束自动释放 | GC管理 |
存储内容 | 局部变量/参数/返回值 | 对象实例 |
大小限制 | 较小(默认1-4MB) | 仅受物理内存限制 |
值类型(Value Type)本质
- 内存行为:直接存储数据本身
- 生命周期:作用域绑定(栈)或依附容器(堆)
- 典型成员:struct/enum/基础类型(int等)
- 关键实验 :
Point p1=p2; p1.X=10
为何不影响p2?
这里假设Point是Struct类型,值类型的赋值是将数据复制了一份。将p2赋值给p1时,是将p2的数据复制了一份,存储在了p1所在的内存空间,此时p1和p2是数据相等但不是同一个数据的情况。
引用类型(Reference Type)本质
- 内存行为:存储对象地址引用
- 生命周期:GC标记清除管理
- 典型成员:class/interface/array/delegate
- 关键实验 :
MyClass o1=o2; o1.Value=10
为何影响o2?
o2赋值给o1时,o1的内存空间存储的是o1数据在堆上的地址,不论对o1操作还是o2操作,都是操作堆上的同一片空间。
o1,o2位于栈上不同的地址,但是其存储的堆上地址索引是相同的。当对o1或者o2操作时,系统识别到这里是引用类型,于是再次在堆上寻找其保存的地址,然后对堆上存储的数据对象进行操作。
数据的存储位置
- 值类型的存储位置误区
- 规则1:局部变量 → 栈存储
- 规则2:类的字段 → 随对象存于堆
- 规则3:数组元素 → 存于堆
- 引用类型的内存双栖性
- 对象本体 → 堆内存
- 引用变量 → 栈/堆(作为其他对象成员时)
装箱与拆箱
-
装箱(Boxing)发生场景
- 值类型赋值给object/interface
- 值类型存入非泛型集合(ArrayList)
- 值类型作为object类型参数传递
-
装箱底层四步流程
int i = 42; // 栈上值 object o = i; // 装箱触发
- 步骤1:堆上分配内存(值大小+对象头)
- 步骤2:复制值数据到堆
- 步骤3:设置类型指针和同步块
- 步骤4:返回对象引用
-
拆箱(Unboxing)核心机制
- 类型安全验证:运行时类型检查
- 地址计算:跳过对象头获取数据区
- 值复制:堆→栈的数据转移
- 危险操作 :
double d=3.14; int i=(int)(object)d;
-
特殊场景剖析
- 接口装箱:
IFormattable f = 42;
- GetType()/ToString()的免装箱优化
- 泛型规避装箱:
List<int>
vsArrayList
- 接口装箱:
性能影响与实战优化
-
量化性能损耗
- 测试对比:1000万次操作
- 纯值类型:List
<int>
: 149ms - 装箱拆箱:ArrayList: 1065ms
- 纯值类型:List
- 内存开销:对象头(8-16字节)带来的空间浪费
// 测试添加1000万次int var sw = Stopwatch.StartNew(); var list = new ArrayList(); for (int i = 0; i < 10_000_000; i++) { list.Add(i); // 装箱 } UnityEngine.Debug.Log($"ArrayList: {sw.ElapsedMilliseconds}ms"); sw.Restart(); var genericList = new List<int>(); for (int i = 0; i < 10_000_000; i++) { genericList.Add(i); // 无装箱 } UnityEngine.Debug.Log($"List<int>: {sw.ElapsedMilliseconds}ms");
- 测试对比:1000万次操作
-
高频陷阱与规避策略
-
陷阱1:循环内的意外装箱
foreach (var n in intArray) { // 当intArray为ArrayList时发生拆箱 }
- 陷阱2:结构体实现接口的隐式装箱
-
优化方案:
- 使用泛型集合
List<T>
- 结构体设计为只读(readonly struct)
- 使用
Span<T>
操作内存
- 使用泛型集合
-
-
高级优化技巧
- 接口约束避免装箱:
where T : struct
- in参数修饰符减少大结构体复制
- Unsafe代码直接操作内存
- 接口约束避免装箱:
总结
- 核心关系三定律
- 值类型是数据本体,引用类型是地址指针
- 栈管瞬时数据,堆管持久对象
- 装箱=值类型→引用类型,拆箱=逆向转换
- 开发实践指南
- ✅ 小数据/临时变量 → 值类型
- ✅ 复杂对象/共享数据 → 引用类型
- 🚫 避免高频路径装箱
- 🔍 大型结构体评估性能影响
- 延伸学习方向
- 垃圾回收机制(GC)细节
- 内存诊断工具(dotMemory/WinDbg)
- 值类型的进阶特性(ref struct)
PREVIOUSSOLID原则