C#中ArrayList运行机制及其涉及的装箱拆箱
- 1.1 基本用法
- 1.1.1 属性
- 1.1.2 方法
- 1.2 内部实现
- 1.3 装箱
- 1.4 拆箱
- 1.5 object对象的相等性比较
- 1.6 总结
- 1.7 其他简单结构类
1.1 基本用法
命名空间: using System.Collections;
1.1.1 属性
Capacity:获取或设置 ArrayList 可包含的元素数目。
Count:获取 ArrayList 中实际包含的元素数目。
Item[Int32]:获取或设置指定索引处的元素。
1.1.2 方法
Add(Object):向 ArrayList 的末尾添加一个元素。
AddRange(ICollection):向 ArrayList 的末尾添加元素集合。
Clear():移除所有元素。
Contains(Object):判断 ArrayList 是否包含特定元素。
IndexOf(Object):返回某个元素在 ArrayList 中的索引。
Insert(Int32, Object):在指定索引位置插入一个元素。
Remove(Object):移除特定元素的第一个匹配项。
RemoveAt(Int32):移除指定索引处的元素。
Sort():对 ArrayList 的元素排序。
ToArray():将 ArrayList 转换为数组。
1.2 内部实现
- ArrayList 是一个非泛型集合,它可以存储任何类型的对象,本质上是一个object类型的数组;
- 类似于List,其初始capacity为0,后依次动态扩容为4 8 16 32……;
- 动态扩容是一项昂贵的操作,因为它涉及到创建新数组和复制元素,需要不断的GC;
- 如果事先知道大概需要存储的元素数量,预先设置一个合理的容量可以提高性能;
- 存储值类型元素时:装箱;
- 将值类型元素取出来转换使用时:拆箱。
ArrayList array1 = new ArrayList();
WriteLine(array1.Capacity); //输出0(初始容量为0)
array1.Add(1);
WriteLine(array1.Capacity); //输出4(首次扩容到4)
array1.Add("two");
array1.Add(3);
array1.Add("four");
array1.Add(5);
WriteLine(array1.Capacity); //输出8(再次扩容到8)
1.3 装箱
是一个将值类型转换为对象类型的过程,涉及到从栈(值类型通常存储的位置)到堆(引用类型存储的位置)的数据复制。大致步骤:
- 当发生装箱时,.NET 运行时在堆上为该值类型分配内存。
- 接着,它将值类型的内容复制到堆上分配的这块内存中。
- 之后,引用类型变量(例如 object)会指向堆上的这个新位置。
1.4 拆箱
拆箱是将引用类型转换回其原始的值类型的过程。拆箱过程涉及从堆到栈的数据复制。大致步骤:
- 拆箱操作首先检查引用类型实例是否确实指向先前装箱的值类型。
- 如果类型匹配,它会从堆上的对象中复制值回栈上的值类型变量。
- 如果类型不匹配,会抛出 InvalidCastException。
1.5 object对象的相等性比较
== 用于判断两个对象引用是否指向同一个对象实例,即它们在内存中的地址是否相同;
Equals 方法用于比较两个对象的值或内容是否相等。这被称为值相等性。
1.6 总结
装箱和拆箱操作都涉及内存分配和数据复制,这在性能上有一定的成本。特别是在频繁执行这些操作的情况下,性能影响可能更为显著。
由于 ArrayList 是一个非泛型集合,它可以存储任何类型的对象。但这种类型的不确定性会导致运行时错误,并且需要频繁的类型转换。因此,在需要存储不同类型数据的场景中可以使用 ArrayList,但现代的 .NET 开发中,推荐使用泛型集合,如 List< T >,因为它们提供了类型安全和更好的性能。
随着泛型的普及,可以通过使用泛型集合(如 List< T >)来避免许多不必要的装箱和拆箱操作。
1.7 其他简单结构类
ArrayList、Queue、Hashtable等也是类似的:
- 都位于命名空间:using System.Collections;
- 内部使用object数组实现,所以元素可以是任意类型。这会导致装箱、拆箱的问题,带来性能开销。
- 都有对应的更加安全的泛型类,例如Stack< T >、Queue< T >、HashSet< T >。