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

mac2022-06-30  22

  前一阵子有幸看到了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 get111 {112 return this._values[index];113 }114 set115 {116 if (index <= Count)117 {118 throw new Exception("索引值太小");119 }120 else121 {122 Add(value);123 }124 }125 }126 127 public void Print() //用于测试128 {129 for (int i = 0; i < this._size; i++) //使用size,而不是使用length130 {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 SetCapacity166 {167 get168 { return this._values.Length; }169 set170 {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; //update187 }188 else189 {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 else219 {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 Current256 {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.Current273 {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),一旦当你深入了到一定程度的时候你会很受启发的!

转载于:https://www.cnblogs.com/suzhiyong1988/p/5069160.html

相关资源:JAVA上百实例源码以及开源项目
最新回复(0)