学习之路二:关于集合和数组内在联系的深入了解

  前一阵子有幸看到了abatei大牛的泛型系列文章,学习了两周左右,大概学会了50%左右,说实话挺难的,有兴趣的朋友可以去看看!

  http://www.cnblogs.com/abatei/archive/2008/02/20/1075760.html ,讲的真不错,很赞的文章!

  在此记录下我的学习感受,欢迎拍砖!

  文章主要讲的是关于List<T>和数组之间的联系!

  1.集合和数组                                  

    数组:大家都知道数组必须指定大小,而且大小一但指定就不能更改了,也就是说数组不能动态的增加容量,那么对于一些需要动态增加容量的需求是实现不了的,那么就出现了ArrayList,不过它不支持泛型,在读取和增加的时候会涉及到大量的装箱和拆箱,就使得性能得到大幅度的下降!

    集合:大部分都是泛型集合,那么就避免了装箱和拆箱操作,性能得到了提高,而且扩展性功能得到了增强,比如List<T>,Dictionary<TValue,TKey>等等!

 

  2.集合和数组的内在联系(以List<T>为例)                    

    其实如果你是个有心人,就可以通过Reflector发现他们之间的联系了!

    前提:数组在内存中的拷贝速度是非常快的,几乎可以不计,可能要跟你机子的配置有关系了!

    举例:(abatei大牛的例子)就是说一个公司占地50亩,由于业务发展,急需扩大地盘,有两种方案

      ① 买一个50亩的地,那么公司就有两个办公地点,缺点是不能统一管理,两个办公地点员工交流不方便!

      ② 买一个100亩的地,公司把原来的50亩卖掉,全部员工都搬到100亩地的公司,那么就能很好的解决第一种方案的问题!

    实现思想:    

    

    总结:通过以上想法,微软就想出了通过数组在内存中的相互拷贝,来动态增加数组的容量,从而使我们在外观上只注意是List能动态增加容量,其实它里面还是通过数组来存储数据的,只是方式不一样罢了!

 

  3.代码实现(自定义List<T>泛型集合)                      

  1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Collections;
6 using System.Diagnostics;
7
8 namespace DemoGenerics
9 {
10 [Serializable, DebuggerDisplay("Count = {Count}")]
11 public class MyList<T> : IEnumerable<T> //,ICollection<T>
12 {

13 private T[] _values; //实际存储数据的数组
14 private static T[] _emptyValues; //用来初始化数组
15 private int _size; //获取集合(数组)中元素的个数
16

17 static MyList()
18 {
19 MyList<T>._emptyValues = new T[0];
20 }
21
22 public MyList()
23 {
24 this._size = 0;
25 this._values = MyList<T>._emptyValues; //初始化实际存储数据的数组
26 }

27
28 public int Count //获取元素个数
29 {

30 get { return this._size; }
31 }
32
33 public int Capacity //获取实际存储数据的容量,也就是数组的大小,注意这不是数组中元素的个数,而是数组的长度
34 {

35 get { return this._values.Length; }
36 }
37
38 public bool Remove(T value)
39 {
40 //先想想如果要删除一个数据有几种方法:
41        //①循环遍历找到那个数据
42 //②除了循环就没有了 哈哈
43 int index = GetIndexOf(value);

44 RemoveAt(index);
45 return true;
46 }
47
48 public bool RemoveAt(int index)
49 {
50 if (index >= this._size) //也可以这样 index > this._size - 1
51 {

52 throw new IndexOutOfRangeException("索引超出范围");
53 }
54 if (index < 0)
55 {
56 throw new Exception("索引值无效");
57 }
58 if((index >= 0) && (index < this._size))
59 {
60 this._size--;
62 //原来数组删除数据是这样的啊
63          //删除数组中的某一项也就是覆盖
64 // 覆盖的数据 + 覆盖开始出的索引 + 接受的数据 + 开始存储的开始出索引 + 复制的项数
65 Array.Copy(this._values, index + 1, this._values, index, this._size - index);

66 //如果索引是 5 也就是第六个项,总共有十项
67 //从第七个项(索引为6)开始复制,往后赋值五个项(10-5),最后从第六个项(索引为5)开始覆盖,那么就会把第六个项覆盖了,最后也就删除了
68 //return true;
69 }

70
71 return true;
72 }
73
74 public void RemoveAll()
75 {
76 Array.Clear(this._values, 0, this._size); //调用数组的方法         
78 this._size = 0;

79 }
80
81 public int GetIndexOf(T value)
82 {
83 //找索引值,其实也是循环
84 for (int i = 0; i < this._values.Length; i++)

85 {
86 if (value.Equals(this._values[i]))
87 {
88 return i;
89 }
90 }
92 throw new Exception("没有找到要删除的项");
93 }
94
95 public void Add(T value) //添加元素方法
96 {

97 if (value.Equals(null))
98 {
99 throw new ArgumentNullException("value");
100 }
102 //int number = Array.BinarySearch<T>(this._values, 0, this.Count, value, null);103
104        //添加
105 Inset(value); //这边可能会有错,先获取数组的长度
106 }

107
108 public T this[int index] //索引器
109 {

110 get
111 {
112 return this._values[index];
113 }
114 set
115 {
116 if (index <= Count)
117 {
118 throw new Exception("索引值太小");
119 }
120 else
121 {
122 Add(value);
123 }
124 }
125 }
126
127 public void Print() //用于测试
128 {

129 for (int i = 0; i < this._size; i++) //使用size,而不是使用length
130 {

131 Console.WriteLine("Value:\t{0}", this._values[i].ToString());
132 }
133 }
134
135 //私有方法,主要是帮助Add方法
136 private void Inset(T value) //, int size) //index为当前数组的长度
137 {

138 //首先判断定义的_size和实际的数组的长度是否相等
139 //如果相等说明数组已经满了,不能再继续增加数据了,那么就要改变它的容量!
140 if (this._size == this._values.Length)

141 {
142 //确保容量正常
143 this.EnsureCapacity(this._size + 1);

144 }
145 this._values[Count] = value;
146 this._size++; //自动增长
147 }

148
149 private void EnsureCapacity(int min) //确保容量正常,只要调用这个方法,数组容量都变为原来的两倍
150 {

151 //判断当前容量,如果原来容量小于当前容量,它的容量范围应该扩大为2倍
152 //所以说只要进行容量调整,容量都会变为以前的两倍(其实就是数组的长度)
153 int capacityNumber = (this._values.Length == 0) ? 4 : (this._values.Length * 2);

154 if (capacityNumber < min)
155 {
156 capacityNumber = min;
157 }
159 //容量调整好了,就要进行数组的初始化
160 //this.InsertSetCapacity(capacityNumber); //使用方法来增加容量
161 this.SetCapacity = capacityNumber;    //使用属性来增加容量

162 }
163
164 //使用属性代替方法看性能提升的如何? 结果没有变化,不知道为什么性能比微软的List<T>要低两三倍,知道的朋友请留言给我!
165 public int SetCapacity

166 {
167 get
168 { return this._values.Length; }
169 set
170 {
171 if (this._size != value) //这边的value就是我重新调整好的数组空间(也可以叫数组长度)
172 {

173 if (value < this._size)
174 {
175 throw new ArgumentOutOfRangeException("value", "要调整的容量值太小了");
176 }
177 if (value > 0)
178 {
179 T[] newValues = new T[value];
180 if (this._size > 0)
181 {
182 //进行数组拷贝了
183 //最后一个参数是我要赋值原数据中多少条记录
184 Array.Copy(this._values, 0, newValues, 0, this._size);

185 }
186 this._values = newValues; //update
187 }

188 else
189 {
190 this._values = MyList<T>._emptyValues;
191 }
192 }
193 }
194 }
195
196 private void InsertSetCapacity(int arrayLength) //使数组以前的容量改变为当前修改的容量
197 {

198 if (arrayLength != this._values.Length) //如果不相等,则说明要调整容量了
199 {

200 if (arrayLength < this._size)
201 {
202 throw new ArgumentOutOfRangeException("arrayLength", "要调整的容量值太小了");
203 }
204 if (arrayLength > 0) //开始调整
205 {

206 //重新开辟内存空间存储数组,指定它的大小
207 T[] localValues = new T[arrayLength];

208
209 if (this._size > 0) //这边如果大于0,说明此时数组有值,那么就要进行数据的迁移!
210 {

211 //数据搬家,也就是拷贝数组,注意参数设置 → (以前的数组,当前需要拷贝的数组,大小)
212 //Array.Copy(this._values, localValues, this._size); //这边_size的大小是以前数组的大小
213 Array.Copy(this._values, 0, localValues, 0, this._size);

214 }
215 //拷贝好了,再指定数组,也就是把原来的旧数据替换成新数据
216 this._values = localValues;

217 }
218 else
219 {
220 //设置容量,空数组
221 this._values = MyList<T>._emptyValues; //指定容量,但是没有数据
222 }

223 }
224 }
228         public IEnumerator<T> GetEnumerator()
229 {
230 return new MyEnumerator((MyList<T>)this); //实现可枚举类型
231 }
237         IEnumerator IEnumerable.GetEnumerator()
238 {
239 throw new NotImplementedException();
240 }
241
244 public class MyEnumerator : IEnumerator<T> //实现枚举数
245 {
246 private MyList<T> _myList; //存储集合
247 private int _index; //索引号
248

249 internal MyEnumerator(MyList<T> myList) //定义构造函数,初始化一些变量,数组
250 {

251 this._myList = myList;
252 this._index = -1; //注意初始化的时候为-1,这是规定
253 }

254
255 public T Current
256 {
257 get
258 {
259 if(_myList._size <= 0)
260 {
261 throw new Exception("空集合");
262 }
263 return this._myList[this._index];
264 }
265 }
266
267 public void Dispose()
268 {
269 GC.Collect();
270 }
271
272 object System.Collections.IEnumerator.Current
273 {
274 get { throw new NotImplementedException(); }
275 }
276
277 public bool MoveNext() //判断是否超出集合项的范围
278 {

279 if (this._index < this._myList._size - 1)
280 {
281 this._index++;
282 return true;
283 }
284 return false;
285 }
286
287 public void Reset()
288 {
289 this._index = -1;
290 this._myList = null;
291 }
292 }
293 }
294 }

    总结:其实那些高级的集合也是采用的这种方式,可能实现的比较复杂,基本原理都是这样子的(能力有限啊)!   

 

  4.最后想说一下关于删除数组中某一项的问题                  

    以前也没注意过数组能不能删除某一项,在现成的类库中是没有Remove或者Delete的方法删除的!

    前几天在用Reflector查看List<T>中Remove代码发现删除数组某一项原来是这样做的,激动ing!

    删除方式:①通过你要删除的项找到这个项的索引号 → ②然后调用抽象类Array中的Copy方法来删除你要删除的项。

    原理如下:    

    

    总结:其实在调用Array中Copy方法后,它最终会调用Windows里面的API函数,通过调用的API函数去内存中删除要删除的项!

    调用外部函数的代码:    

   [MethodImpl(MethodImplOptions.InternalCall), ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
   internal static extern void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable);
  --------------------------------------------------------------------------
 总结:说实话能想到这种方法的人真的很棒,很有创新意识,其实当你学习知识的时候要一定注意它身后的一些东西(尤其是微软的.NET Framework),一旦当你深入了到一定程度的时候你会很受启发的!
 在学习中深入,在实践中提高,厚积薄发!
 下一篇:关于Linq的深入了解
posted @ 2011-12-23 20:10  TimYang  阅读(3220)  评论(6编辑  收藏  举报