ArrayList
如果要动态地改变数组所占用内存空间的大小则需以数组为基础进一步抽象以实现这个功能以图的学生宿捨为例为了使A班的所学生住在连续的宿捨内可以把A班的学生全部搬迁到连续的间空宿捨内其效果如图所示
现实中为了让一个班新加入的个学生能跟原来的学生住在一起而把班级整体搬迁这样的做法显示不合适因为搬迁的成本太高但在计算机中内存成片区域间的拷贝成本是非常低的这样的解决方案是合理可行的
但是这个解决方案还存在问题如果一个班级频繁地有新学生加入为了保证学生能住在连续的宿捨内整个班级就不得不频繁地搬迁可以采用以空间换时间的做法来解决这个问题在学生每次搬迁时都让班级宿捨的数量是原来的两倍也就是说如果原来一个班级有间宿捨搬迁后就变为间再次搬迁则变为间如图所示A班的宿捨为~~这三间宿捨做为本班备用宿捨不再允许其他班级的学生搬入
C#中的ArrayList正是采用上述方法来动态改变数组大小的ArrayList又被称为动态数组它的存储空间可以被动态改变同时还拥有添加删除元素的功能
下面列出了ArrayList的部分核心代码
【ArrayListcs】
using System;
namespace LinearList
{
public class ArrayList
{
// 成员变量
private const int _defaultCapacity = ; //默认初始容量
private object[] _items; //用于存放元素的数组
private int _size; //指示当前元素个数
//当元素个数为零时的数组状态
private static readonly object[] emptyArray = new object[];
// 方法
public ArrayList() //默认构造方法
{ //这样做可以避免元素个数为零时的访问出错
this_items = emptyArray;
}
//指定ArrayList初始容量的构造方法
public ArrayList(int capacity)
{
if (capacity < )
{ //当容量参数为负数时引发异常
throw new ArgumentOutOfRangeException(capacity
为ArrayList指定的初始容量不能为负数);
}
//按照capacity参数指定的长度的值初始化数组
this_items = new object[capacity];
}
//添加一个方法
public virtual int Add(object value)
{ //当空间满时
if (this_size == this_itemsLength)
{ //调整空间
thisEnsureCapacity(this_size + );
}
this_items[this_size] = value; //添加元素
return this_size++; //使长度加
}
//动态调整数组空间
private void EnsureCapacity(int min)
{
if (this_itemsLength < min)
{ //空间加倍
int num = (this_itemsLength == ) ?
_defaultCapacity : (this_itemsLength * );
if (num < min)
{
num = min;
}
//调用Capacity的set访问器以按照num的值调整数组空间
thisCapacity = num;
}
}
//在指定索引入插入指定元素
public virtual void Insert(int index object value)
{
if ((index < ) || (index > this_size))
{
throw new ArgumentOutOfRangeException(index 索引超出范围);
}
if (this_size == this_itemsLength)
{ //当空间满时调整空间
thisEnsureCapacity(this_size + );
}
if (index < this_size)
{ //插入点后面的所有元素向后移动一位
ArrayCopy(this_items index
this_items index + this_size index);
}
this_items[index] = value; //插入元素
this_size++; //使长度加
}
//移除指定索引的元素
public virtual void RemoveAt(int index)
{
if ((index < ) || (index >= this_size))
{
throw new ArgumentOutOfRangeException(index 索引超出范围);
}
this_size; //使长度减
if (index < this_size)
{ //使被删除元素后的所有元素向前移动一位
ArrayCopy(this_items index +
this_items index this_size index);
}
this_items[this_size] = null; //最后一位空出的元素置空
}
//裁减空间使存储空间正好适合元素个数
public virtual void TrimToSize()
{
thisCapacity = this_size;
}
// 属性
public virtual int Capacity //指示ArrayList的存储空间
{
get
{
return this_itemsLength;
}
set
{
if (value != this_itemsLength)
{
if (value < this_size)
{
throw new ArgumentOutOfRangeException(value 容量太小);
}
if (value > )
{ //开辟一块新的内存空间存储元素
object[] destinationArray = new object[value];
if (this_size > )
{ //把元素搬迁到新空间内
ArrayCopy(this_items
destinationArray this_size);
}
this_items = destinationArray;
}
else //最小空间为_defaultCapacity所指定的数目这里是
{
this_items = new object[_defaultCapacity];
}
}
}
}
public virtual int Count //只读属性指示当前元素个数
{
get
{
return this_size;
}
}
public virtual object this[int index] //索引器
{
get //获取指定索引的元素值
{
if ((index < ) || (index >= this_size))
{
throw new ArgumentOutOfRangeException(index 索引超出范围);
}
return this_items[index];
}
set //设置指定索引的元素值
{
if ((index < ) || (index >= this_size))
{
throw new ArgumentOutOfRangeException(index 索引超出范围);
}
this_items[index] = value;
}
}
}
}
上述代码通过在一个数组(第行代码的成员变量_items)的基础上做进一步抽象构建了一个可动态改变空间的顺序表ArrayList并实现了一些基础操作下面对之进行一一介绍
初始化
这里实现了种初始方法第一种为~行代码它把顺序表空间初始化为一个长度数组这样做的目的是为了调用方便做为成员变量的object类型数组_items默认会被初始化为null如果不把它初始化为长度数组在使用代码 ArrayList arr = new ArrayList() 来创建ArrayList后试图访问它的Count属性将会导致错误发生
第二种初始化方法为~行代码它根据capacity参数所指定的值来初始化_items数组的长度如果初始化一个长度为的ArrayList数组可以使用如下代码
ArrayList arr = new ArrayList()
当可以预见ArrayList所操作的大概元素个数时使用这种方法可以在一定程序上避免数组重复创建和数据迁移以提高性能和减少内存垃圾回收的压力
动态改变存储空间操作
~行的EnsureCapacity(int min)方法用于空间不足时使空间加倍从代码
int num = (this_itemsLength == ) ? _defaultCapacity : (this_itemsLength * );
可以得知当元素个数为是空间增长为否则将翻倍改变空间大小的代码是在Capacity属性中的set访问器中实现的(代码~行)代码
object[] destinationArray = new object[value];
创建了一个新的object数组它在内存中开辟了一个新的空间用于存放元素代码
ArrayCopy(this_items destinationArray this_size);
把_items数组中的元素全部拷贝到新数组destinationArray中可以把它理解为数据搬新家最后通过
this_items = destinationArray;
使用于存放数据的成员变量_items指向新的数组对象destinationArray
~行的TrimToSize()方法用于裁减多余空间实际的裁减操作也是在Capacity属性中的set访问器中实现这个操作也会导致数组的重新创建和数据迁移建议一般情况下不使用此操作除非集合中的剩余空间很多
元素的读写操作
~行代码实现了一个索引器这样就可以使用中括号加索引号来读取和给元素赋值使ArrayList的使用看上去和数组很相似
元素的添加和插入操作
~行的Add(object value)方法实现了添加元素的功能元素添加在集合的末尾成员变量_size用于指示当前元素个数它总是指向集合中的最后一个元素
~行的Insert(int index object value)方法用于在指定索引处插入一个元素为了保证顺序表中的每个元素物理上相邻插入点后面的所有元素都将后移一位其效果如图(a)所示
元素的删除操作
~行的RemoveAt(int index)方法用于删除指定索引的元素删除指定元素后删除点后的所有元素将向前移动一位其效果如图(b)所示
下面对ArrayList类进行测试
【例】ArrayList的使用
新建一个控制台应用程序并在项目中把上面的ArrayListcs文件做为一个【现有项】添加进去在代码窗体前面使用如下语句加入LinearList命名空间
using LinearList;
并在Main方法中输入如下代码
using System;
using LinearList;
namespace Demo_
{
class Program
{
static void Main(string[] args)
{
ArrayList arr = new ArrayList();
ConsoleWriteLine(arr现在的容量为 + arrCapacity + 长度为: + arrCount);
arrAdd(); //添加一个元素
ConsoleWriteLine(arr现在的容量为 + arrCapacity + 长度为: + arrCount);
for (int i = ; i <= ; i++)
{ //添加个元素完成后元素总数达到个
arrAdd(i);
}
ConsoleWriteLine(arr现在的容量为 + arrCapacity + 长度为: + arrCount);
for (int i = ; i <= ; i++)
{ //添加个元素完成后元素总数达到个
arrAdd(i);
}
ConsoleWriteLine(arr现在的容量为 + arrCapacity + 长度为: + arrCount);
for (int i = ; i < arrCount; i++) //打印所有元素
{
ConsoleWrite(i + );
}
//删除两个元素
arrRemoveAt(arrCount );
arrRemoveAt(arrCount );
ConsoleWriteLine(); //换行
for (int i = ; i < arrCount; i++) //打印所有元素
{
ConsoleWrite(i + );
}
ConsoleWriteLine(); //换行
ConsoleWriteLine(arr现在的容量为 + arrCapacity + 长度为: + arrCount);
arrTrimToSize(); //载减多余空间
ConsoleWriteLine(arr现在的容量为 + arrCapacity + 长度为: + arrCount);
ConsoleReadLine();
}
}
}
运行结果如图所示
由运行结果可以得知数组对象arr的容量随着元素的不断增加从→→→不断改变在删除两个元素之后容量还保持在不变在通过调用TrimToSize()裁减空间后容量最终变为
类型安全
数组和ArrayList的本质区别在于前者是类型安全的而后者不是类型安全的ArrayList为了兼容所有类型的对象使用了object数组这给使用带来了一些的麻烦如下例所示
【例】数组和ArrayList的对比
本例使用了C#类库中的ArrayList而不是前面自定义的ArrayList它存在于SystemCollections命名空间中新建一个控制台应用程序引入SystemCollections命名空间并在Main()方法中输入如下代码
using System;
using SystemCollections;
namespace Demo_
{
class Program
{
static void Main(string[] args)
{
int[] arr = new int[];
arr[] = ;
arr[] = ;
int result = arr[] * arr[];
ConsoleWriteLine(result);
ArrayList arrL = new ArrayList();
arrLAdd();
arrLAdd();
result = (int)arrL[] * (int)arrL[];
ConsoleWriteLine(result);
ConsoleReadLine();
}
}
}
运行结果
本例使用数组和ArrayList分别做了相同的事情但使用方法却大相径庭首先数组在创建时就已经确定只接收int类型数据并且它的长度是固定的而ArrayList则可以接收任意object类型而事实上C#中的所有类均是object类型的子类
其次数组没有添加元素的功能因为在数组创建时各个元素就已经存在只是被初始化为而已只能通过下标改变各个元素的值而ArrayList只有把元素添加进去后才可以通过下标访问相应的元素
最后在使用集合中的元素时数组不需要进行强制类型转换而ArrayList必须要经过强制类型转换才能使用这是因为ArrayList实际存放的是object对象要按照这些对象原本的类型来使用就必须要使用强制类型转换
ArrayList的这个特点带来了类型安全问题如
ArrayList arrL = new ArrayList();
arrLAdd();
arrLAdd(Hello World);
arrLAdd(new Button());
以上代码在集合中存放了各种各样的数据类型但这样做是被允许的这种类型的不安全一方面给程序带来了隐患另一方面如果集合中存放的是值类型还会产生装箱和拆箱操作降低了程序的性能
NET 版本的泛型的出现完美地解决了上述问题新版本使用SystemCollectionsGeneric命名空间下的List<T>类取代了原来的ArrayList类下面演示了泛型List<T>类的使用
List<int> arrL=new List<int>();
arrLAdd();
arrLAdd();
可以看到第一行代码在集合创建时就已经把元素类型限定为int它是类型安全的同时避免了装箱和拆箱操作强烈建议在实际编程中使用List<T>代替ArrayList