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


      前一阵子有幸看到了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),一旦当你深入了到一定程度的时候你会很受启发的!
  • 相关阅读:
    Struts之上传
    Struts之准备工作
    前端--关于背景、浮动和定位
    javascript学习目录
    audio和video元素
    js实现动态操作table
    jquery全选、反选、全不选
    js实现页面跳转
    10 个非常有用的 AngularJS 框架
    JavaScript 语言基础知识点总结(思维导图)
  • 原文地址:https://www.cnblogs.com/suzhiyong1988/p/5069160.html
Copyright © 2020-2023  润新知