zoukankan      html  css  js  c++  java
  • 集合

    选择集合

    一般情况下,应使用泛型集合。 下表介绍了一些常用的集合方案和可用于这些方案的集合类。 如果你是使用泛型集合的新手,此表将帮助你选择最适合你的任务的泛型集合。

    选择集合
    我要……泛型集合选项非泛型集合选项线程安全或不可变集合选项
    将项存储为键/值对以通过键进行快速查找 Dictionary<TKey,TValue> Hashtable

    (根据键的哈希代码组织的键/值对的集合。)
    ConcurrentDictionary<TKey,TValue>

    ReadOnlyDictionary<TKey,TValue>

    ImmutableDictionary<TKey,TValue>
    按索引访问项 List<T> Array

    ArrayList
    ImmutableList<T>

    ImmutableArray
    使用项先进先出 (FIFO) Queue<T> Queue ConcurrentQueue<T>

    ImmutableQueue<T>
    使用数据后进先出 (LIFO) Stack<T> Stack ConcurrentStack<T>

    ImmutableStack<T>
    按顺序访问项 LinkedList<T> 无建议 无建议
    删除集合中的项或向集合添加项时接收通知。 (实现 INotifyPropertyChanged 和 INotifyCollectionChanged ObservableCollection<T> 无建议 无建议
    已排序的集合 SortedList<TKey,TValue> SortedList ImmutableSortedDictionary<TKey,TValue>

    ImmutableSortedSet<T>
    数学函数的一个集 HashSet<T>

    SortedSet<T>
    无建议 ImmutableHashSet<T>

    ImmutableSortedSet<T>

    HashSet<T> 类是用于包含唯一元素的无序集合。SortedSet<T> 类提供在执行插入、删除和搜索操作之后让数据一直按排序顺序排列的自平衡树。

    抽象基类 KeyedCollection<TKey,TItem> 的行为类似列表和字典。

     

     ConcurrentStack、ConcurrentQueue和ConcurrentBag类型内部是使用链表实现的。因此,其内存利用不如非并发的Stack和Queue高效。但是它们适用于并发访问,因为链表更容易实现无锁算法或者少锁的算法。

     

     ConcurrentBag<T> 表示对象的线程安全的无序集合,与集不同,包支持重复项。ConcurrentBag<T> 可以接受 null 作为引用类型的有效值。

    一个ConcurrentBag<T>对象上的每一个线程都有自己的私有链表。线程在调用Add方法时会将元素添加到自己的私有链表中,因此不会出现竞争。当我们枚举集合中的元素时,其枚举器会遍历每一个线程的私有链表,依次返回

    在调用Take时,ConcurrentBag<T>首先会查询当前线程的私有列表,如果列表中至少有一个元素存在[插图],那么该操作就可以在不引入竞争的情况下完成。但是,如果私有列表是空的,则必须从其他线程的私有列表中“窃取”一个元素,而这种操作可能造成竞争。
    因此,准确地说,Take方法将返回调用线程在集合中最近添加的元素。如果该线程上已经没有任何元素,它会返回其他线程(随机挑选)最近添加的元素。

     

    集合的算法复杂性

    不可变的集合类型通常性能较低,但却提供了不可变性,这通常是一种非常有效的优点。

    集合的算法复杂性
    可变分期最坏情况不可变复杂性
    Stack<T>.Push O(1) O(n) ImmutableStack<T>.Push O(1)
    Queue<T>.Enqueue O(1) O(n) ImmutableQueue<T>.Enqueue O(1)
    List<T>.Add O(1) O(n) ImmutableList<T>.Add O(log n)
    List<T>.Item[Int32] O(1) O(1) ImmutableList<T>.Item[Int32] O(log n)
    List<T>.Enumerator O(n) O(n) ImmutableList<T>.Enumerator O(n)
    HashSet<T>.Add, lookup O(1) O(n) ImmutableHashSet<T>.Add O(log n)
    SortedSet<T>.Add O(log n) O(n) ImmutableSortedSet<T>.Add O(log n)
    Dictionary<T>.Add O(1) O(n) ImmutableDictionary<T>.Add O(log n)
    Dictionary<T> lookup O(1) O(1) - 或者从严格意义上说,O(n) ImmutableDictionary<T> lookup O(log n)
    SortedDictionary<T>.Add O(log n) O(n log n) ImmutableSortedDictionary<T>.Add O(log n)

    由于其索引器的 O(log n) 时间,ImmutableList<T> 在 for 循环内的效果较差。 使用 foreach 循环枚举 ImmutableList<T> 很有效,因为 ImmutableList<T> 使用二进制树来存储其数据,而不是像 List<T> 那样使用简单数组。 数组可以非常快速地编入索引,但必须向下遍历二进制树,直到找到具有所需索引的节点。

    此外,SortedSet<T> 与 ImmutableSortedSet<T> 的复杂性相同。 这是因为它们都使用了二进制树。 当然,显著的差异在于 ImmutableSortedSet<T> 使用不可变的二进制树。 由于 ImmutableSortedSet<T> 还提供了一个允许变化的 System.Collections.Immutable.ImmutableSortedSet<T>.Builder 类,因此可以同时实现不可变性和保障性能。

    检查是否相等

    诸如 Contains、 IndexOf、 LastIndexOf和 Remove 的方法将相等比较器用于集合元素。 如果集合是泛型的,则按照以下原则比较项是否相等:

    此外,字典集合的某些构造函数重载接受 IEqualityComparer<T> 实现,用于比较键是否相等。 

    确定排序顺序

    对于比较对象,有 default comparer 和 explicit comparer的概念。

    默认比较器依赖至少一个正在被比较的对象来实现 IComparable 接口。 在用作列表集合的值或字典集合的键的所有类上实现 IComparable 不失为一个好办法。 对泛型集合而言,等同性比较是根据以下内容确定的:

    为了提供显式比较,某些方法接受 IComparer 实现作为参数。 例如, List<T>.Sort 方法接受 System.Collections.Generic.IComparer<T> 实现。

    SortedList<TKey,TValue> 类与 SortedDictionary<TKey,TValue> 类之间的一些区别。

    表 1
     SortedList<TKey,TValue> 泛型类SortedDictionary<TKey,TValue> 泛型类
    返回键和值的属性是有索引的,从而允许高效的索引检索。 无索引的检索。
    检索属于 O(log n) 操作。 检索属于 O(log n) 操作。
    插入和删除通常属于 O(n) 操作;不过,对于已按排序顺序排列的数据,插入属于 O(log n) 操作,这样每个元素都可以添加到列表的末尾。 (这假设不需要调整大小。) 插入和删除属于 O(log n) 操作。
    比 SortedDictionary<TKey,TValue> 使用更少的内存。 比 SortedList 非泛型类和 SortedList<TKey,TValue> 泛型类使用更多内存。

    对于必须可通过多个线程并发访问的已排序列表或字典,可以向派生自 ConcurrentDictionary<TKey,TValue> 的类添加排序逻辑

    并发集合类型使用轻量同步机制,如 SpinLockSpinWaitSemaphoreSlim 和 CountdownEvent,这些机制是 .NET Framework 4 中的新增功能。 这些同步类型通常在将线程真正置于等待状态之前,会在短时间内使用忙旋转。 预计等待时间非常短时,旋转比等待所消耗的计算资源少得多,因为后者涉及资源消耗量大的内核转换。 对于使用旋转的集合类,这种效率意味着多个线程能够以非常快的速率添加和删除项。

     

    BlockingCollection<T> 是一个线程安全集合类,可提供实现制造者-使用者模式。多个线程或任务可同时向集合添加项,如果集合达到其指定最大容量,则制造线程将发生阻塞,直到移除集合中的某个项。 多个使用者可以同时移除项,如果集合变空,则使用线程将发生阻塞,直到制造者添加某个项。 制造线程可调用 CompleteAdding 来指示不再添加项。 使用者将监视 IsCompleted 属性以了解集合何时为空且不再添加项。

    创建 BlockingCollection<T> 时,不仅可以指定上限容量,而且可以指定要使用的集合类型。 例如,可为先进先出 (FIFO) 行为指定 ConcurrentQueue<T>,也可为后进先出 (LIFO) 行为指定 ConcurrentStack<T>。 可使用实现 IProducerConsumerCollection<T> 接口的任何集合类。 BlockingCollection<T> 的默认集合类型为 ConcurrentQueue<T>

    在使用者需要同时取出多个集合中的项的情况下,可以创建 BlockingCollection<T> 的数组并使用静态方法,如 TakeFromAny 和 AddToAny 方法,这两个方法可以在该数组的任意集合中添加或取出项。 如果一个集合发生阻塞,此方法会立即尝试其他集合,直到找到能够执行该操作的集合。

    //Generate some source data.
          BlockingCollection<int>[] sourceArrays = new BlockingCollection<int>[5];
          for(int i = 0; i < sourceArrays.Length; i++)
              sourceArrays[i] = new BlockingCollection<int>(500);
          Parallel.For(0, sourceArrays.Length * 500, (j) =>
                              {
                                  int k = BlockingCollection<int>.TryAddToAny(sourceArrays, j);
                                  if(k >=0)
                                      Console.WriteLine("added {0} to source data", j);
                              });
    
          foreach (var arr in sourceArrays)
              arr.CompleteAdding();

    ConcurrentDictionary<TKey,TValue> 专为多线程方案而设计。 无需在代码中使用锁定即可在集合中添加或移除项。 但始终可能出现以下情况:一个线程检索一个值,而另一线程通过为同一键赋予新值来立即更新集合。

    此外,尽管 ConcurrentDictionary<TKey,TValue> 的所有方法都是线程安全的,但并非所有方法都是原子的,尤其是 GetOrAdd 和 AddOrUpdate。 为避免未知代码阻止所有线程,传递给这些方法的用户委托将在词典的内部锁之外调用。 因此,可能发生以下事件序列:

    1. threadA 调用 GetOrAdd,未找到项,通过调用 valueFactory 委托创建要添加的新项。

    2. threadB 并发调用 GetOrAdd,其 valueFactory 委托受到调用,并且它在 threadA 之前到达内部锁,并将其新键值对添加到词典中 。

    3. threadA 的用户委托完成,此线程到达锁位置,但现在发现已有项存在。

    4. threadA 执行“Get”,返回之前由 threadB 添加的数据 。

    因此,无法保证 GetOrAdd 返回的数据与线程的 valueFactory 创建的数据相同。 调用 AddOrUpdate 时可能发生相似的事件序列。

    Microsoft.Extensions.ObjectPool 命名空间下已存在 Microsoft.Extensions.ObjectPool.ObjectPool<T> 类型。  在需要某个类的多个实例并且创建或销毁该类的成本很高的情况下,对象池可以改进应用程序性能。 

     

  • 相关阅读:
    Golang实现mysql where in 查询
    Golang终止程序运行(类似php die; exit;)和打印变量(print_r)
    (转)Unity中protobuf的使用方法
    (转)PlayerPrefs游戏存档
    Unity3d---> IEnumerator
    (转)Unity3D占用内存太大的解决方法
    UICamera(NGUI Event system)原理
    NGUI诡异的drawCall
    (转)U3D DrawCall优化手记
    (转)Unity3D
  • 原文地址:https://www.cnblogs.com/yetsen/p/13531889.html
Copyright © 2011-2022 走看看