• C#集合:List、Queue、Stack和Set


    .NET Core提供了一些基本的具体集合类,这些类实现了一系列集合接口。和集合接口一样,每一种集合类型都可以选择使用泛型或非泛型进行实现。在灵活性和性能方面,泛型类更具有优势,而它们的非泛型冗余版本则是为了向后兼容。这与集合接口不同,非泛型集合接口在某些情形下是有其作用的。而泛型List类是最常用的。

    List<T>和ArrayList

    泛型List和非泛型ArrayList类都提供了一种可动态调整大小的对象数组实现,是集合类中使用最广泛的。ArrayList实现了IList,而List<T>既实现了IList又实现了IList<T>。与数组不同,所有的接口都是公开实现的,而且其方法如Add和Remove也都是公开可用的。

    List<T>ArrayList在内部都维护着一个对象数组,并在超出容量时替换为一个更大的数组。在集合中追加元素的效率很高(因为数组末尾一般都有空闲的位置),而插入元素的速度会慢一些(因为插入位置之后的所有元素都必须向后移动才能留出插入空间),移除元素同样速度较慢(尤其是移除起始元素时)。

    List<T>ArrayList具有可以接受现有集合的构造器,它们会将现有集合中的每一个元素都复制到新的List<T>ArrayList中。

    public class List<T> : ICollection<T>, IEnumerable<T>, IEnumerable, IList<T>, 
        IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection, IList
    {
            public List();
            public List(IEnumerable<T> collection);
            public List(int capacity);
    
            // 追加、插入
            public void Add(T item);
            public void AddRange(IEnumerable<T> collection);
            public void Insert(int index, T item);
            public void InsertRange(int index, IEnumerable<T> collection);
            
            // 删除
            public bool Remove(T item);
            public void RemoveAt(int index);
            public void RemoveRange(int index, int count);
            public int RemoveAll(Predicate<T> match);
            
            // 索引
            public T this[int index] { get; set; }
            public List<T> GetRange(int index, int count);
            public List<T>.Enumerator GetEnumerator();
            
            // 复制、转换
            public T[] ToArray();
            public void CopyTo(T[] array, int arrayIndex);
            public void CopyTo(T[] array);
            public void CopyTo(int index, T[] array, int arrayIndex, int count);
            public ReadOnlyCollection<T> AsReadOnly();
            public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter);
            
            public void Reverse(int index, int count);
            public void Reverse();
            public int Capacity { get; set; }
            public void TrimExcess();
            public void Clear();
    }
    

    除了上述成员,List<T>还提供了Array类中所有搜索和排序方法的实例版本。

    List<string> words = new List<string>();    
    
    words.Add ("melon");
    words.Add ("avocado");
    words.AddRange (new[] { "banana", "plum" } );
    words.Insert (0, "lemon");                           // 开头插入
    words.InsertRange (0, new[] { "peach", "nashi" });   // 开头插入
    
    words.Remove ("melon");
    words.RemoveAt (3);                         // 删除第四个
    words.RemoveRange (0, 2);                   // 从第一个开始删除2个
    
    words.RemoveAll (s => s.StartsWith ("n"));  // 删除开头有n的
    
    Console.WriteLine (words [0]);                          // first word
    Console.WriteLine (words [words.Count - 1]);            // last word
    foreach (string s in words) Console.WriteLine (s);      // all words
    List<string> subset = words.GetRange (1, 2);            // 2nd->3rd words
    
    string[] wordsArray = words.ToArray();    
    
    // 将前两个元素复制到现有数组的末尾
    string[] existing = new string [1000];
    words.CopyTo (0, existing, 998, 2);
    
    List<string> upperCaseWords = words.ConvertAll (s => s.ToUpper());
    List<int> lengths = words.ConvertAll (s => s.Length);
    

    使用非泛型的ArrayList类往往需要进行烦琐的转换,如下所示:

    ArrayList al = new ArrayList();
    al.Add ("hello");
    string first = (string) al [0];
    string[] strArr = (string[]) al.ToArray (typeof (string));
    

    编译器无法验证这些转换,所以像int first = (int)al[0];这个例子会在运行时出错。

    如果导入了System.Linq命名空间,那么可以先调用Cast再调用ToList将一个ArrayList转换为一个泛型List。Cast和ToList都是System.Linq.Enumerable类的扩展方法。

    ArrayList al = new ArrayList();
    al.AddRange (new[] {1,5,9});
    List<int> list = al.Cast<int>().ToList();
    

    LinkedList<T>

    LinkedList<T>是一个泛型的双向链表。
    双向链表是一系列相互引用的节点,每一个节点都引用前一个节点、后一个节点以及实际存储的数据元素。它的主要优点是元素总能够高效地插入到链表的任意位置,因为插入节点只需要创建一个新节点,然后修改引用值。然而查找插入节点的位置会比较慢,因为链表本身并没有直接索引的内在机制。必须遍历每一个节点,并且无法执行二分搜索。
    image

    LinkedList<T>实现了IEnumerable<T>ICollection<T>(及它们的非泛型版本),但是没有实现IList<T>,因为它不支持索引访问。链表节点是通过下面的类实现的:

    public sealed class LinkedListNode<T>
    {
        public LinkedListNode(T value);
        public LinkedList<T>? List { get; }
        public LinkedListNode<T>? Next { get; }
        public LinkedListNode<T>? Previous { get; }
        public T Value { get; set; }
        public ref T ValueRef { get; }
    }
    

    当添加一个节点时,可以指定它相对于其他节点的位置,或者指定它位于链表的开始/结束位置。可以使用以下方法为LinkedList<T>添加节点:

        public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode);
        public LinkedListNode<T> AddAfter(LinkedListNode<T> node, T value);
    
        public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode);
        public LinkedListNode<T> AddBefore(LinkedListNode<T> node, T value);
    
        public void AddFirst(LinkedListNode<T> node);
        public LinkedListNode<T> AddFirst(T value);
    
        public void AddLast(LinkedListNode<T> node);
        public LinkedListNode<T> AddLast(T value);
    

    LinkedList<T>内部的一些字段记录了链表元素的个数以及链表的头部和尾部。可以通过下面的公有属性访问这些信息:

        public LinkedListNode<T>? Last { get; }
        public LinkedListNode<T>? First { get; }
        public int Count { get; }
    

    也支持以下搜索方法:

        public bool Contains(T value);
        public LinkedListNode<T>? Find(T value);
        public LinkedListNode<T>? FindLast(T value)
    

    最后,可以将LinkedList<T>的元素复制到一个数组中,以便支持索引处理。Linked-List<T>也支持foreach语句所需的枚举器:

        public void CopyTo(T[] array, int index);
        public LinkedList<T>.Enumerator GetEnumerator();
    

    以下代码演示了LinkedList<String>的用法:

    var tune = new LinkedList<string>();
    tune.AddFirst ("do");                            // do
    tune.AddLast ("so");                             // do - so
    
    tune.AddAfter (tune.First, "re");                // do - re- so
    tune.AddAfter (tune.First.Next, "mi");           // do - re - mi- so
    tune.AddBefore (tune.Last, "fa");                // do - re - mi - fa- so
    
    tune.RemoveFirst();                              // re - mi - fa - so
    tune.RemoveLast();                               // re - mi - fa
    
    LinkedListNode<string> miNode = tune.Find ("mi");
    tune.Remove (miNode);                            // re - fa
    tune.AddFirst (miNode);
    

    Queue<T>和Queue

    Queue<T>Queue是先进先出(FIFO)的数据结构。
    它们提供了Enqueue(将一个元素添加到队列末尾)和Dequeue(取出并删除队列的第一个元素)方法。它们还包括一个只返回而不删除队列第一个元素的Peek方法以及一个Count属性(可在取出元素前检查该元素是否存在于队列中)。

    public class Queue<T> : IEnumerable<T>, IEnumerable, IReadOnlyCollection<T>, ICollection
    {
        public Queue();
        public Queue(IEnumerable<T> collection);
        public Queue(int capacity);
    
        public int Count { get; }
        public void Clear();
        public bool Contains(T item);
        public void CopyTo(T[] array, int arrayIndex);
        public T Dequeue(); // 取出并删除队列的第一个元素
        public void Enqueue(T item); // 将一个元素添加到队列末尾
        public Queue<T>.Enumerator GetEnumerator();
        public T Peek();
        public T[] ToArray();
        public void TrimExcess();
    }
    

    虽然队列是可枚举的,但是它并没有实现IList<T>IList,因为我们无法直接通过索引访问其成员。然而,可以使用ToArray方法将其中的元素复制到一个数组中,而后进行随机访问:

        var q = new Queue<int>();
        q.Enqueue (10);
        q.Enqueue (20);
        int[] data = q.ToArray();         // 转为一个数组
        Console.WriteLine (q.Count);      // "2"
        Console.WriteLine (q.Peek());     // "10"
        Console.WriteLine (q.Dequeue());  // "10"
        Console.WriteLine (q.Dequeue());  // "20"
        Console.WriteLine (q.Dequeue());  // 抛出一个异常(队列为空)
    

    队列的实现和泛型List类相似,都在内部使用了一个可根据需要进行大小调整的数组。队列具有一个直接指向头部和尾部元素的索引,因此其入队和出队的操作速度非常快。

    Stack<T>和Stack

    Stack<T>Stack是后进先出(LIFO)的数据结构。
    它们提供了Push(向栈的顶部添加一个元素)和Pop(从栈顶取出并删除一个元素)方法,也提供了一个只读取而不删除元素的Peek方法、Count属性,以及可以导出数据并进行随机访问的ToArray方法:

    public class Stack<T> : IEnumerable<T>, IEnumerable, IReadOnlyCollection<T>, ICollection
    {
        public Stack();
        public Stack(IEnumerable<T> collection);
        public Stack(int capacity);
        public int Count { get; }
        public void Clear();
        public bool Contains(T item);
        public void CopyTo(T[] array, int arrayIndex);
        public Stack<T>.Enumerator GetEnumerator();
        public T Peek(); // 从栈顶取出一个元素
        public T Pop(); // 从栈顶取出并删除一个元素
        public void Push(T item);  // 向栈的顶部添加一个元素
        public T[] ToArray();
        public void TrimExcess();
    }
    

    使用方法:

        var s = new Stack<int>();
        s.Push(1);
        s.Push(2);
        s.Push(3);
        Console.WriteLine(s.Count);  // 输出 3
        Console.WriteLine(s.Peek()); // 1,2,3 输出 3
        Console.WriteLine(s.Pop());  // 1,2 输出 3
        Console.WriteLine(s.Pop());  // 1 输出 2
        Console.WriteLine(s.Pop());  // <empty> 输出 1
        Console.WriteLine(s.Pop());  // 报错
    

    栈和Queue<T>List<T>一样,其内部也是用一个可以根据需要调整大小的数组实现的。

    BitArray

    BitArray是一个压缩保存bool值的可动态调整大小的集合。
    由于它使用一位(而不是一般的一字节)来存储一个bool值,因此比起简单的bool数组和以bool为类型参数的泛型List,BitArray具有更高的内存使用效率。BitArray的索引器可以读写每一位:

        var bits = new BitArray(2);
        bits[1] = true;
    

    它提供了四种按位操作的运算符方法:And、Or、Xor和Not。除最后一个方法外,其他的方法都接受一个BitArray作为参数:

        bits.Xor(bits); // 与自身按位异或
        Console.WriterLine(bits[1]); // false
    

    HashSet<T>和SortedSet<T>

    HashSet<T>SortedSet<T>分别是在.NET Framework 3.5和4.0版本新增的泛型集合类型。
    它们都具有以下特点:

    • 它们的Contains方法均使用散列查找,因而执行速度很快。
    • 它们都不保存重复元素,并且都忽略添加重复值的请求。
    • 无法根据位置访问元素。SortedSet<T>按一定顺序保存元素,而HashSet<T>则不是。

    HashSet<T>是通过使用只存储键的散列表实现的,而SortedSet<T>则是通过一个红黑树实现的。

    两个集合都实现ICollection<T>并提供了一些常用的方法,例如Contains、Add和Remove。此外还提供了一个基于谓词的删除元素的方法:RemoveWhere。

        var letters = new HashSet<char> ("the quick brown fox");
        // 是否包含某些成员
        Console.WriteLine (letters.Contains ('t'));      // true
        Console.WriteLine (letters.Contains ('j'));      // false
    

    真正有意思的方法是集合的各种操作。以下集合操作是破坏性的,即它们会修改集合:

        // 将第二个集合的所有元素添加到原始集合上(不包含重复元素)
        public void UnionWith(IEnumerable<T> other);  
        // 将不属于两个集合共有的元素删除
        public void IntersectWith(IEnumerable<T> other); 
        // 删除源集合中的指定元素
        public void ExceptWith(IEnumerable<T> other); 
        // 删除两个集合中共有的元素
        public void SymmetricExceptWith(IEnumerable<T> other);
    

    下面的方法仅仅对集合进行查询,因而是非破坏性的:

        public bool IsProperSubsetOf(IEnumerable<T> other);
        public bool IsProperSupersetOf(IEnumerable<T> other);
        public bool IsSubsetOf(IEnumerable<T> other);
        public bool IsSupersetOf(IEnumerable<T> other);
        public bool Overlaps(IEnumerable<T> other);
        public bool SetEquals(IEnumerable<T> other);
    

    HashSet<T>SortedSet<T>均实现了IEnumerable<T>,因此也可以使用另外一种Set类型或者集合类型作为集合操作方法的参数。
    SortedSet<T>拥有HashSet<T>的所有成员。除此之外,还有如下的成员:

        public virtual SortedSet<T> GetViewBetween(T? lowerValue, T? upperValue);
        public IEnumerable<T> Reverse();
        public T? Min { get; }
        public T? Max { get; }
    

    SortedSet<T>的构造器还可接受一个可选的IComparer<T>参数(而非一个相等比较器)。
    下面的例子会把相同的字符加载到SortedSet<char>中,获得f与j之间的字符。

        var letters = new SortedSet<char> ("the quick brown fox");
        foreach (char c in letters) 
            Console.Write (c);    //  bcefhiknoqrtuwx
        foreach (char c in letters.GetViewBetween ('f', 'i'))
            Console.Write (c);    //  fhi
    
  • 相关阅读:
    Silverlight5.0新特性一览
    Silverlight在线考试系统项目
    Silverlight生产线组装设计器案例
    Silverlight案例之卷轴动画和Tag树
    风云收到微软正版Windows7正式旗舰版DVD光盘
    微软Windows Live Hotmail加入Silverlight相册
    Silverlight4实现三维企业网站
    Silverlight版Web操作系统(Silverlight WebOS)
    Silverlight 实现RIA端到端的优势
    Silverlight 博客整站源代码+数据库(完整版下载)
  • 原文地址:https://www.cnblogs.com/nullcodeworld/p/16636783.html
Copyright © 2020-2023  润新知