FCL(3):: ArrayList & List (1)


ArrayList是以前最常用的一个Collection. <<.net设计规范>>第八章一条:  要优先使用集合,而不是优先使用数组. 因ArrayList比Array好用多了:)
记得以前面试的时候主考官问我用过ArrayList没,答:用过; 问:与Array有什么区别.答:这个...这个...记不起来了.事后坐公交的时候,突然想到ArrayList不就是Array的List,数组链表吗.狂拍自己脑袋.所幸主考官还是让我pass了,有幸进入到现在的公司.

用反射看了一下ArrayList,其实就是对一个Array的封装.就如<<Maximizing .Net Performance>>:  "The only collection implemented directly at a runtime level is System.Array, and all other collection types are implemented using Array." ArrayList底层用的数据对象就是一个Object的 Array.只不过它提供了一个List功能的接口.初始化默认的大小是4,以前咋听说是16呢.

当我们调用其Add()方法时,首先会去判断一下是否有足够的空间  if (this._size == this._items.Length), 不够的话就调用EnsureCapacity()方法,确保其已分配足够的空间.在申请新的一片内存空间时,会调用Array.Copy()方法将原有的数据复制到新的地址.(哈哈,跟我以前大学学C#做过的一个练习,实现的方式是一样的).这个不仅让我想到了一个性能问题.数据结构里面的Array的List模型可非如此.应该是当申请完一片新的空间后,原来旧空间的最后一个位置通过指针链接到新空间的首地址.这样的话就不需要拷贝原来数据到新的空间地址里了.而且数据可以储存在不连贯的空间上.更加合理地利用了空间.当然这样的实现会更复杂些,带来的最大好处就是频繁扩大空间时不需要拷贝一次旧的数据.当然通过索引来访问数据,可能没有用一个数组那样访问那么快了.
所以说在ArrayList初始化的时候,应该给它一个初始的Capacity(如果大致知道需要容器的大小,或者在需要很大的情况下,尽量稍微分大一点),尽量避免多次重新分配空间,避免频繁拷贝数据带来的开销.

接着说说ArrayList.InsertRange(int index, ICollection c) 和 List<T>.InsertRange(int index, IEnumerable<T> collection).仔细观察一下两个方法,它们第二个参数的类型是不一样的.ICollection是继承自IEnumerable的.这个改变体现了<<.net设计规范>>第八章一条:  要用最范的类型作为参数类型.另一条是:  避免使用ICollection<T>或ICollection来做参数,如果其目的只是为了访问该接口的Count属性.还举了个例子,说 可以考虑用IEnumerabler作为参数,然后动态地检查一下对象,看它有没有实现ICollection.
在此也有个概念可以得到明确:实现了IEnumerabler接口的类就是一个集合,不要以为ICollection接口上有个Collection就认为集合一定要实现此接口.(不过一般都会实现此接口 :))
再说List<T>.InsertRange()方法,因为是 IEnumerable<T>类型参数,我们拿不到它的Count,于是会先将其转型为ICollection<T>,看是否成功,成功的话就可以只需调用一次EnsureCapacity(),一次性的分配足够的空间就行了.然后调用ICollection<T>.CopyTo()方法一次性Copy就好了.如果不是,那只能逐个逐个遍历再Insert了,性能肯定是要损失了.不过貌似看到的集合类都是直接或间接实现了ICollection或ICollection<T>接口.

这里让我想到了前几天跟Leader的一个关于设计的争论.焦点在于他说这种向下转型(如IEnumerabler --> ICollection)代码不优雅,而需要设计出一个辅助类去获得一个真正我们想要的子类型.而我觉得这样添加一些额外的操作却根本没带来什么好处,向下转型本身就很简洁,很平常的事.理由就是: 在设计框架的时候它为了实现通信必须提供它自己的契约:一组接口,而我们在应用的时候需要去扩充我们自己的功能,于是在实现了框架的契约后会提供一组自己的接口.但同时框架在通信的时候只知道自己定的契约,而不知道我们的类,我们扩充的功能,所以有时候向下转型是避免不了,也是不为过的.
再看List<T>.InsertRange()方法,因为它定义的参数类型是IEnumerable<T>,那么它就可以接受更宽泛的参数来源.然而在其内部实现的时候,虽然可以完全用IEnumerable<T>操作,遍历它然后一个一个调用Insert.但这样的性能肯定是要被人唾弃的.一个简单的向下转型,转为ICollection<T>就能带来性能的提升,何乐而不为呢.
我现在就可以更理直气壮地说,瞧FCL里面实现也是避免不了向下转型,你能说这样一定就不优雅了?

下一片继续探讨一下同步的ArrayList,和一个搞笑的GetRange()方法.

posted @ 2007-05-08 14:04  Anders06  阅读(1545)  评论(2编辑  收藏  举报