C# Data Structure - 2
Wenqi Huang
3 min read
引用类型 (Reference Type):
在C#中,引用类型不直接存储数据值,而是存储一个引用或指针,该引用指向存储在堆上的对象。
特点:
- 存储引用:不存值,只存数据位置的引用。同一个对象,可被多个 Reference 引用,从而实现数据共享。
- 在堆上分配:引用类型的对象,在堆上分配空间,对象的生存周期更灵活,可以在堆上分配和释放内存。
- 需要垃圾回收:由于在堆上分配,内存管理由垃圾回收器负责。垃圾回收器会自动检测不再被引用的对象并释放其占用的内存。
常见的C#引用类型:
- 类(Class):包含数据成员和方法成员,并可以用于创建对象实例。
- 接口(Interface):定义了一组抽象方法,提供给类来继承。
- 委托(Delegate):用于引用方法,通常用于事件处理和回调机制。
- 字符串(String):在C#中,是引用类型。
- 动态类型(dynamic):dynamic类型是一种在运行时解析的引用类型,它可以包含任何类型的值。
- 数组(Array):虽然数组存储的是连续的数据元素,但数组本身是引用类型。数组变量存储了对数组对象的引用。
在 VS 中查看一个数据类型,是 引用类型 还是 值类型,用 F12, 如:
- int --> public struct Int32
- string --> public sealed class String
- object --> public class Object
值类型 (Value Type):
在 C# 中,值类型,直接存储数据的值在内存中,而不是存储其位置。
特点:
- 存储值本身:值类型直接存储数据值,而不是引用内存位置。
- 通常在栈上分配:值类型的实例通常在执行栈上分配内存,而不是堆上。这使得值类型的创建和销毁更加高效,因为它们的生命周期通常与其作用域相关。
- 不需要垃圾回收:由于值类型的内存管理是在栈上进行的,它们不受垃圾回收的影响。当变量超出作用域时,它们的内存将自动释放。
常见的C# 值类型 包括:
- 整数类型:包括 int、short、long、byte 等。
- 浮点数类型:包括 float 和 double。
- 布尔类型:bool,表示真或假。
- 字符类型:char,表示单个字符。
- 枚举类型:enum,表示一组具名的常量值。
- 结构体类型:自定义的值类型可以通过定义结构体(struct)来创建。
- Nullable 类型:允许值类型变量表示一个可空值,通过在类型名称后加上 ? 创建,如: int?。
- 元组(Tuple):元组是一种可以包含多个值的值类型,通常用于组合不同类型的数据。
装箱: 值类型 --> 引用类型
拆箱: 引用类型 --> 值类型
泛型数组 List<> 有两个优势:
- 对于存储 值类型 的数据,性能更优。
- 使代码更清晰,保证类型安全,List只能存储同一种类型的数据。
一个数组,存贮不同类型元素,有两种方法:
ArrayList: 将特定类型的数组,改为 Object 类型的数组。object 是所有类型的父类。
- 取出元素,将object类型的元素,强制转换成特定类型(拆箱)
- 放入元素,将特定元素,强制转换成object(装箱)
- ArrayList() 存储的就是 Object 类型的引用类型数据,所以可以混合存储不同类型的数据。但频繁的装箱拆箱,十分影响性能。已近淘汰。不推荐!
通过泛型,来存贮不同类型的元素。
- List<泛型>,泛型的一大优点,是可以避免频繁的 装箱和拆箱,存贮值类型时,性能更好。
- 代码更清晰,而且能保证类型安全。
案例 1:测试值类型对象 int
int n = 10000000; // int 的最大位数是10位
// 计时器 1),测试值类型对象 int
Stopwatch t1 = Stopwatch.StartNew();
Stopwatch t2 = Stopwatch.StartNew();
Stopwatch t3 = Stopwatch.StartNew();
Stopwatch t4 = Stopwatch.StartNew();
Console.WriteLine("Test Value Type: int");
t1.Start();
List<int> int_list = new List<int>();
for (int i = 0; i < n; i++)
{
int_list.Add(i); // 没有类型转换,不发生 装箱
int x = int_list[i]; // 没有类型转换,不发生 拆箱
}
t1.Stop();
// n是100万,88ms,快一倍 -- n是1000万,179 ms,快10倍
Console.WriteLine("List's time: " + t1.ElapsedMilliseconds + "ms");
案例 2:测试引用类型对象 ArrayList for int
// 计时器 2) 测试引用类型对象 ArrayList for int
Console.WriteLine("Test Referrence Type: ArrayList for int");
t2.Start();
ArrayList a = new ArrayList();
for (int i = 0; i < n; i++)
{
a.Add(i); // 发生 装箱, int --> object
int x = (int)a[i]; // 发生 拆箱, (int)强制将 object --> int
}
t2.Stop();
// n是100万,166ms,慢一倍 -- n是1000万,1710 ms,慢10倍
Console.WriteLine("ArrayList's time: " + t2.ElapsedMilliseconds + "ms");
案例 3:测试引用类型对象 String
Console.WriteLine("Test Referrence Type: String");
t3.Start();
List<string> string_list = new List<string>();
for (int i = 0; i < n; i++)
{
string_list.Add("x"); // 不发生 装箱
string x = string_list[i]; // 不发生 拆箱
}
t3.Stop();
Console.WriteLine("String's time: " + t3.ElapsedMilliseconds + "ms");
案例 4:测试引用类型对象 ArrayList for string
Console.WriteLine("Test Referrence Type: ArrayList for string"); // 274 ms,快50%
t4.Start();
ArrayList b = new ArrayList();
for (int i = 0; i < n; i++)
{
b.Add("x"); // 不发生 装箱, string --> object,都是引用类型
string x = (string)b[i]; // 不发生 拆箱, (string)强制将 object --> string
}
t4.Stop();
Console.WriteLine("ArrayList's time: " + t4.ElapsedMilliseconds + "ms"); // 331 ms,慢50%
性能测试运行结果:
VS中,性能测试要用 Release,而不是 Debug
- List<泛型> 在存贮 值类型 时,要比 ArrayList 快的多,相差10多倍。 Test Value Type: int List's time: 215ms Test Referrence Type: ArrayList for int ArrayList's time: 2125ms
- List<泛型> 在存贮 引用类型 时,要比 ArrayList 快,但相差的不多。 Test Referrence Type: String String's time: 2325ms Test Referrence Type: ArrayList for string ArrayList's time: 2568ms
结论:
- List 泛型 和 ArrayList 都能存贮不同类型的元素,但 List 的性能更优!
- List 在存贮 Value Type 时,要比 ArrayList 快的多。
- List 在存贮 Reference Type 时,要比 ArrayList 快,但相差的不多。
0
Subscribe to my newsletter
Read articles from Wenqi Huang directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by