.Net有两类基础的集合类型:List和Dictionary。List是基于Index的,Dictionary是基于key的。集合类型一般实现了IEnumberable,ICollection或者Ilist 接口。
类型 | 描写叙述 | 使用场景 |
ArrayList | 可以依据加入项动态调整集合大小。 |
|
Hashtable | 基于Key的hash值。存储key/value对的collection类型。 |
|
HybridDictionary | 可以依据集合的大小,动态使用对应类型的字典类型。使用ListDictionary(小)。使用Hashtable(大)。 |
|
ListDictionary | 基于Key的hash值,存储key/value对的collection类型,基于单链表实现的。 |
|
NameValueCollection | 存储string类型的key/value的可排序的Collection。 |
|
SortedList | 基于Key排序的Key/value对的collection。能够通过key或index来提取value。 |
|
StringCollection | string类型的arraylist |
|
StringDictionary | 基于Hash table。 用于存储key是string类型的Dictionary |
|
Queue | 基于ICollection的实现。先进先出 |
|
Stack | 后进先出 |
|
集合类型常见问题:
- Boxing issues
- Thread safety
- Enumeration overhead
Boxing issues
假设使用ArrayList去存储值类型时都会被运行装箱操作。当取数据时会运行拆箱操作。
当要存储大量的item时会导致过度开销。
ArrayList al = new ArrayList();
for (int i = 0; i < 1000; i++)
al.Add(i); //Implicitly boxed because Add() takes an object
int f = (int)al[0]; // The element is unboxed
解决方式:
考虑使用强类型的数组取代。或者使用自己定义集合类型。
在.net framework 2.0之后,使用泛型能够避免装箱和拆箱的开销。
Thread Safety
通常,集合默认不是线程安全的。在多线程下对于读操作是线程安全的,对于不论什么改动操作是非线程安全的,会导致未知的结果。
解决方式:
- 使用Synchronized方法
//初始化
ArrayList arrayList = new ArrayList();
//加入对象到集合
…….
//使用Synchronized方法
Arraylist syncArrayList = ArrayList.Synchronized(arrayList);
- 使用Lock语句的SyncRoot属性
ArrayList myCollection = new ArrayList();
Lock(myCollection.SyncRoot){}
Enumeration Overhead
.Net Framework 1.1 通过重写IEnumerable.GetEnumberator 方法提供了枚举器。
但也不是非常理想,理由例如以下:
- GetEnumerator方法是虚拟的,所以无法内联。
- GetEnumerator方法返回的是Ienumerator的接口而不是一个实际的类型。因此,在编译期间,无法确定实际的枚举器(Enumerator)
- MoveNext方法和Current属性相同是虚拟的,因此无法内联。
- IEnumerator.Current返回的的是Object类型。依据存储在集合的数据类型可能须要装箱和拆箱操作。
因此在使用foreach对集合进行操作时会面临托管堆和虚拟方法的开销。
Collection Guidelines
- Analyze your requirements before choosing the collection type.(选择集合类型之前进行需求分析)
- Initialize collections to the right size when you can.(当你能确定集合的大小时,初始化集合时指定合适的大小)
- Consider enumerating overhead.(考虑枚举开销)
- Prefer to implement IEnumerable with optimistic concurrency.(实现IEnumerable接口去实现开放式并发)
- Consider boxing overhead.(考虑装箱和拆箱开销)
- Consider for instead of foreach.(考虑使用for取代foreach)
- Implement strongly typed collections to prevent casting overhead.(实现强类型的集合类型去防止强制转换开销)
- Be efficient with data in collections.(高效利用集合中的数据)
Analyze your requirements before choosing the collection type.
- Do you need to sort your collection?
- ArrayList适用于仅仅读已排序的数据作为数据源
- SortedList适用于须要排序的静态不常常更新的数据,通常。(当构造集合时SortedList预先排序数据,创建排序列举的过程是比較昂贵的。可是不论什么对数据的更新都能自己主动且高效的对集合进行又一次排序)
- NameValueCollection适用于须要排序的字符串集合。
- Do you need to search your collection?
- 假设你会依据key/value随机的进行search,使用HashTable。
- 假设对字符串进行随机的search,使用StringDictionary。
- 对于集合大小小于10的进行search使用ListDictonary
- Do you need to access each element by index?
- 对于基于0索引開始的集合。使用ArrayList和StringCollection
- 通过Key来取数据,使用HashTable,SortedList,ListDictionary,StringDictionary。
- NameValueCollection既能够使用索引又能够通过key取数据。
- 集合(Array)通过索引取数据比不论什么集合类型都高效。
- Do you need a custom collection?
- 当你想通过引用类型封送集合。
- 你须要创建一个强类型集合来避免转换开销(假设创建的强类型集合是继承自CollectionBase 或者Hashtable。你依然无法避免转换开销。)。
- 你须要创建一个仅仅读集合。
- 你须要对你的强类型集合提供序列化。
- 你须要注意枚举的开销。
Initialize collections to the right size when you can.
- 尽可能的指定集合的大小,能够更好的提高集合性能。
Consider enumerating overhead.
- 假设实现了Ienumerable.GetEnumerator,同一时候实现一个非虚拟的GetEnumerator方法。
class MyClass : IEnumerable
{
// non-virtual implementation for your custom collection
public MyEnumerator GetEnumerator() {
return new MyEnumerator(this); // Return nested public struct
}
// IEnumerator implementation
public IEnumerator.GetEnumerator() {
return GetEnumerator();//call the non-interface method
}
}
foreach调用非虚拟的GetEnumerator会比通过接口调用虚拟方法更高效。
- 实现IEnumerator.Current属性。
// Custom property in your class
//call this property to avoid the boxing or casting overhead
Public MyValueType Current {
MyValueType obj = new MyValueType();
// the obj fields are populated here
return obj;
}
// Explicit member implementation
Object IEnumerator.Current {
get { return Current} // Call the non-interface property to avoid casting
}
Prefer to implement IEnumerable with optimistic concurrency.
- 确保集合不会被改动当集合正在被枚举。
- 採用快照在枚举器中。
Consider boxing overhead.
当在集合中存储值类型时,装箱开销会依据集合的大小。更新或操作数据的频率影响。
假设不须要集合过多的功能,尽量使用Array.
Consider for instead of foreach.
在性能敏感的代码中尽量使用for.
Implement strongly typed collections to prevent casting overhead.
Be efficient with data in collections.
当处理大量的对象时。处理每一个对象的大小会十分重要。