那些年你用的集合
.NET有超过20种内置的集合类型,.NET Framework中有些集合只是为了保持向后兼容性,新的代码种绝不应该再去使用以下集合
ArrayList
Hashtable
Queue
SortedList
Stack
ListDictionary
HybridDictionary
为啥呢?因为会发生类型转换和装箱操作。,上述集合类保存的都是对Object实例的引用,所以你总要转换未实际的对象类型,装箱是最严重的问题。
试着用这些集合
1:假设访问有一个Int32类型的ArrayList,每个成员都会单独装箱并保存在内存堆中,为了访问所有的Int32成员,并不是像对联系存放的内存数组那样遍历,而是需要对每个引用进行一次指针间接引用,再访问内存堆(丧失了就近访问可能性),在进行一次拆箱操作获取内部的数据值。所以下面可替换为
Dictionary实现为一个哈希表,插入和读取 操作的时间复杂度为O(1)
SortedDictionary实现为二叉树,插入和读取操作的时间复杂度为O(log n)
SortedList实现为有序数组,读取操作的时间为O(log n),最坏为O(n),内存需求最少,
HashSet 使用了哈希表,插入和删除操作O(1)
SortedSet使用了二叉树,插入和删除操作O(log n)
List,StarcK,Queue在内部都使用了数组,操作数据量大的时候效率较高,事先如果知道成员的大小,应该把所需的内存预先分配好,这样是避免CPU和GC开销去调整数组的大小。
并发访问的集合类
1|0System.Collections.Concurrent命名空间
ConcurrentBag<T>
ConcurrentDictionary<TKey,TValue>
ConcurrentQueue<T>
ConcurrentStack<T>
......
大部分内容使用了Interlocked或Monitor作为同步原语
以上需要注意
TryXXX方法,但当其他线程正在操作而发生冲突时,就会失败
如ConcurrentStarkTryPop当其他线程正在操作而发生冲突时,就会返回false,
ConcurrentDictionary可调用TryAdd,或者用TryUpdate。
AddOrUpdate不能直接给出新数据,而是需要传入两个委托,分别用于添加和修改操作.
第一个委托,键不存在,返回对应的值
第二个委托,键存在,返回新值或已存在的值
其实不管键存在不存在,都会返回新值,你需要明白一个新值可能并非来自当前线程发起的AddOrUpdate调用,这些方法是线性安全的,但不是原子性,可能其他线程用同一个键调用了方法,第一个线程返回了第二个线程返回的值。
为啥不直接传入数据,而用委托,因为很多时候给的键生成值是一件开销很大的操作,你不希望两个线程同时进行这种操作,委托可以让你使用现有的数据,而不是在生成一份新的拷贝。委托不一定只会调用一次,如果要保证添加和修改的同步性,还需要在委托中添加同步代码。
1:SortedDictionary<TKey,TValue> 类
表示根据键进行排序的键/值对的集合。
比较:泛型类是具有 O (log n)检索的二进制搜索树,其中 n 是字典中的元素数。 在这方面,它类似SortedList<TKey,TValue>于泛型类。 这两个类具有相似的对象模型,两者都有 O (log n)检索。 这两个类的不同之处在于内存使用和插入和删除的速度:
- SortedList<TKey,TValue>使用比SortedDictionary<TKey,TValue>更少的内存。
- SortedDictionary<TKey,TValue>为未排序的数据提供更快的插入和删除操作:O (log n),而不是 O (n) SortedList<TKey,TValue>。
- 如果列表是从已排序数据一次填充的, SortedList<TKey,TValue>则速度比SortedDictionary<TKey,TValue>快。
使用TryGetValue方法作为更高效的方法来检索值,如果程序必须经常尝试在已排序的列表中,不必显示如何使用ContainsKey方法来测试某个键是否存在之前调用Add方法。
关于SortedList<TKey,TValue>,SortedDictionary<TKey,TValue>使用出现的问题
其键不允许重复。使用SortedSet<T>进行解决。
参考:
https://github.com/dotnet/platform-compat/blob/master/docs/DE0006.md