C# 集合

一、集合接口和类型

  大多数的集合类都可在 System.Clooections 和System.Collections.Generic名称空间中找到。泛型集合类位于 System.Collections.Specialized名称空间中。线程安全的集合类位于 System.Collections.Concurrent 名称空间中。

  集合和列表实现的接口如下

 

 二、列表

  .NET Framework 为动态列表提供了泛型类 List<T>。这个类实现了 IList、ICollection、IEnumerable、IList<T>、ICollection<T> 和 IEnumerable<T> 接口。

  1、创建列表

    调用默认的构造函数,就可以创建列表对象。在泛型类中,必须为列表的值指定类型。

    使用默认的构造函数创建一个空列表后,元素添加到列表后,列表的容量就会扩大为可接纳4个元素,如果添加了5个元素,就会,设置大小为8,还不够,就16。每次都会重新设置为原来的2倍。

    如果创建时指定了大小,每次扩容也是原来的2倍。

    使用 Capacity 可以获取和设置集合的容量。

    Count属性 获取集合中元素个数。

    如果需要去掉不需要的容量,可以用 TrimExcess() 方法。但内容超过容量的90%此方法就什么都不做了。

    集合初始值设定项:后面加上 { } 设置

    添加元素:Add( value ) 方法,AddRange( value1, value2, ...) 可以添加多个

    插入元素:Insert(Index, value) 方法,指定位置,InsertRange(index1,value1,index2,value2, ...) 可以插入多个

    访问元素:支持索引器访问(即直接用下标);用Count() 确认元素个数,for 循环遍历;因为实现了 IEnumerable 接口,所以也可以用 foreach 语句遍历。除了使用foreach语句外,List<T>类还提供了ForEach() 方法,该方法用 Action<T> 参数声明。使用直接为ForEach()方法传递一个方法,无返回值。在T 项列表中,ForEach() 方法的处理程序必须声明为以 T 对象作为参数,返回类型是 void。因为Console.WriteLine() 方法的一个重载版本将Object作为参数,所以直接可以将这个方法的地址传给ForEach() 方法。

    racers.ForEach(Console.WriteLine);

    Lambda 版本:  racers.ForEach(r => Console.WriteLine($"{r}"));

    删除元素:RemoveAt(Index);Remove(value)(不推荐,会先用IndexOf() 查找出位置后删除)(indexOf() 方法先检查元素类型是否实现了 IEquatable<T> 接口,如果是,就调用这个接口的 Equals() 方法。如果没有,就使用Object类的Equals() 方法比较这些元素。Object 类中Equals() 方法默认实现代码对值类型进行按位比较,对引用类型只比较其引用)。Clear() 方法,删除所有,ICollection<T>接口中的。

    搜索:IndexOf()、LastIndexOf()、FindIndex()、FindLastIndex()、Find() 和 FindLasst()。如果只是检查元素是否存在,List<T>类就提供了Exists() 方法。

      IndexOf() 找到返回该元素的索引,没有找到就返回 -1

      搜索由某个特性的元素,用FindIndex() 方法来定义,FindIndex() 方法需要一个Predicate 类型的参数,Predicate<T>类型是一个委托,该委托返回一个布尔值,并且需要把 T 作为参数。true找到,false没找到。在List<T>类中,把 Racer 对象作为类型 T ,所以可以将一个方法的地址传递给 FindIndex() 方法,(该方法将类型 Racer(自定义的一个赛车手类) 定义为一个参数且返回一个布尔值)。比如说查找一个国家的第一个赛车手时,就可以创建一个FindCountry类,FindCountryPredicate() 方法的签名和返回类型通过 Predicate<T> 委托定义。里面的Find() 方法使用变量 country的 搜索用 FindCountry 类构造函数定义的国家。

复制代码
 1     public class FindCountry
 2     {
 3         private string country;
 4         public FindCountry(string country)
 5         {
 6             this.country = country;
 7         }
 8         public bool FindCountryPredicate(Racer racer)
 9         {
10             if (racer == null) throw new ArgumentNullException("racer");
11             return racer.Country == country;
12         }
13     }
复制代码

      使用FindIndex() 方法可以创建 FindCountry() 方法的一个新实例,把一个表示国家的字符串传递给构造函数,再传递 其中Find() 方法的地址。FindIndex() 方法成功完成后,index1 就包含了集合中赛车手的Country 属性设置为 USA 的第一项索引。

        int index1 = racers.FindIndex(new FindCountry("USA").FindCountryPredicate );

      这里使用 Lambda 表达式来处理的话,结果一样,但是代码量大大减少。

        int index2 = racers.FindIndex(r => r.Country=="USA"  );

      FindLastIndex() 方法是从后往前找

      Find() 是直接返回元素,相反的是,FindLast() 从后面开始找 

      FindAll() 方法是返回一个集合

    排序:

      List<T>类可以使用Sort() 方法对元素进行排序,使用的是快速排序算法。

      Sort() 方法使用了4个重载方法,如下

        public void List<T>.Sort();
        public void List<T>.Sort(Comparsion<T>);
        public void List<T>.Sort(IComparer<T>);
        public void List<T>.Sort(Int32, Int32, IComparer<T>);

      只有在集合中的元素实现了IComparable 接口,才能使用不带参数的Sort() 方法。

      如果需要以其他方式进行排序,就需要使用其他技术,例如传递一个实现了IComparaer<T> 接口(就是实现Compara() 方法)的对象。一般会用到 string 和 int 类型的 CompareTo() 方法。

      用的时候直接传 新实例化的实现类中某个属性,例如:

        racers.Sort(new RacerComparer(RaceComparer.CompareType.Country ));

      另一种方法就是使用重载的Sort() 方法,需要一个Comparison<T>委托;该委托有两个T类型的参数,返回类型为int.T1 == T2 返回 0 ,T1 < T2 返回小于 0,T1 > T2 返回大于 0 。

        public delegate int Comparsion<T>( T x, T y );    

      这边也可以直接用Lambda 表达式传递,这里实现降序,所以用 r2 和 r1 作比较。

        racers.Sort((r1, r2) => r2.Wins.CompareTo(r1.Wins) );

    类型转换:

      ConverAll<TOutput>() 方法。可以把所有类型的集合转换为另一种类型。  ConverAll<TOutput>() 方法需要使用一个 Conveter委托

        public sealed delegate TOutput Converter<TInput, TOutput>(TIput from);
        //TInput转换为TOutput,TInput为参数,TOutput为返回类型

      比如将赛车手Racer 转换为一个Person 类,后者只有一个name 类型,转换就只进行name 属性转换,其他的可以忽略,Lambda 表达式如下

List<Person> persons = racers.ConvertAll<Person>( r => new Person(r.FirstName + " " + r.LastName));

  2、只读集合

    创建集合后,就是可读写的。需要得到只读集合,List<T> 集合的AsReadOnly() 方法返回 ReadOnlyCollection<T> 类型的对象。ReadOnlyCollection<T> 类实现的接口和 List<T> 集合相同,但是所有修改集合的方法和属性都会抛出 NotSupportedException 异常。

 

三、队列

  FIFO 先进先出的集合,使用 System.Collections.Generic 名称空间中的泛型类 Queue<T>实现。它是实现ICollection 和 IEnumerable<T> 接口,但没有实现 ICollection<T> 接口,因为这个接口的 Add() 和 Remove() 方法不能用于队列。

  因为没有实现 IList<T> 接口,所以不能使用索引器访问队列,只有 Enqueue() 方法在队尾添加元素,Dequeue() 从队列头部获取元素。它的方法有

 

 

四、栈

  LIFO 后进先出,Pusn() 方法添加,Pop() 方法获取最近添加的元素

  与Queue<T> 类相同,Statck<T> 类实现 IEnumerable<T> 和 ICollection 接口。方法有

 

   可以使用 foreach 方法,其使用IEnmuerable 接口迭代所有的元素。此方法不会删除元素,只会逐个返回元素。

 

五、链表

  LinkedList<T> 是一个双向链表,元素指向它前面和后面的元素,就是存了三个,Value值、Next地址和Previous 地址。这也是LinkedList<T> 中包含了LinkedListNode<T>类型的元素的原因。优点是将元素插入中间的位置,用链表会非常快;缺点是只能一个一个访问,需要用较长的时间来访问中间或者尾部的元素。

  LinkedListNode<T> 定义了属性 List、Next、Previous 和 Value。List属性返回与节点相关的LinkedList<T> 对象,Next 和 Previous 属性用于遍历链表,访问当前节点之后和之前的节点。Value 返回与节点相关的元素,其类型是 T 。

  LinkedList<T> 类定义的成员可以访问链表中的第一个和最后一个元素他(Frist 和 Last)、在指定位置插入元素( AddAfter()、AddBefore()、AddFirst() 和 AddLast() 方法),删除指定位置的元素( Remove()、RemoveFirst() 和 RemoveLast() 方法)、从链表的开头(Find() 方法)或结尾(FindLast() 方法)开始搜索元素。

 

六、有序列表

  如果基于键对所需集合排序,使用 SortedList<TKey, TValue> 类。这个类按照键给元素排序。

  此类只能一个键对应一个值,如果每个键对应多个值,可以使用Lookup<Tkey, TElement> 类。

  可以使用foreach 语句遍历,枚举器返回的元素是 KeyValuePair<TKey, TValue> 类型。也可以使用Values 和 Keys 属性访问值和键。Values 返回 IList<TValue>,Keys属性返回 IList<TKey>。

 

七、字典

  字典是一种复杂的数据结构,这种数据结构允许按照某个键来访问元素。字典也称为映射或散列表。其主要特性是能根据键快速查找值,也可以自由添加和删除元素。,这和List<T>类相似,但没有在内存中移动后续元素的性能开销。

   一个添加到字典中的键,会转换成一个散列,利用散列创建一个数字,它将索引和值关联起来。然后索引包含一个到值的链接。

  .NET Framework 提供了几个字典类。最常用的就是 Dictionary<TKey, TValue> 类。

  1、键的类型

    作为字典中键的类型必须重写Object 类的 GetHashCode() 方法。只要字典类需要确定元素的位置,它就要调用 GetHashCode() 方法。GetHashCode() 方法返回的 int 由字典用于计算在对应位置放置元素的索引。其算法涉及素数,字典的容量是一个素数。

    GetHashCode() 方法的实现代码必须满足如下要求:

    -相同的对象总是返回相同的值

    -不同的对象可以返回相同的值

    -它执行得比较快,计算开销不大

    -它不能抛出异常

    -它应至少使用一个实例字段

    -散列代码值应平均分布在 int 可以存储的整个数字范围上

    -散列代码最好在对象的生存期中不发生变化

    字典的性能取决于 GetHashCode() 方法的实现代码。

    还需要实现 IEquatable<T>.Equals() 方法,或者重写 Object 类的 Equals() 方法。当 A.Equals(B)方法返回 true ,则A.GetHashCode() 和 B.GetHashCode() 方法必须总是返回相同的散列代码。

    System.String 实现了IEquatable 接口,并重载了 GetHashCode() 方法。Equals() 方法提供了值得比较,GetHashCode() 方法根据字符串的值返回一个散列代码。所以在字典中把字符串用作键非常方便。

    如果需要用作的键类型,没有实现 IEquatable 接口,也没有重载 GetHashCode() 方法,就可以创建一个实现 IEqualityComparer<T> 接口的比较器。这里面有GetHashCode() 和 Equals() 方法,并将传递的对象作为参数。

  2、Lookup 类

    一对多,一个键映射到一个值集上,调用方法 ToLookup(),该方法返回一个 Lookup<TKey, TElement>对象。List<T> 类实现了IEnumerable<T> 接口,所以可以直接调用ToLookup() 方法。此方法需要一个 Func<TSorce, TKey>类型的委托。

  3、有序字典

    SortedDictionary<TKey, TValue>类是一个二叉搜索树。与SortList<TKey, TValue>类功能相似。前者是一个字典,后者是一个基于数组的列表。

 

八、集

   set,没有重复的元素。HashSet<T> 和 SortSet<T> 两个集,一个无序,一个有序。

  ISet<T> 接口提供的方法可以创建合集、交集,或者给出一个集是另一个集的超集或子集的信息。

  Add() 方法返回true或者false,表示是否添加成功。

  IsSubsetOf(),A.IsSubsetOf(B),A是否为B的子集

  IsSupsetOf(),A.IsSupsetOf(B),A是否为B的超集(即B是否为A的子集)

  UnionWith(),合集

  ExceptWith(),A.ExceptWith(B)删除A中的B

 

九、可观察的集合

  需要了解集合中的元素何时删除或添加的信息,可以使用 ObservableCollection<T> 类。

  可以通过接收 NotifyCollectionChangeedEventArgs,这里面包含的Action属性可以得出结果,示例代码如下:

复制代码
  
 1 using System;
 2 using System.Collections.ObjectModel;
 3 
 4 namespace Wrox.ProCSharp.Collections
 5 {
 6     class Program
 7     {
 8         static void Main()
 9         {
10             var data = new ObservableCollection<string>();
11             data.CollectionChanged += Data_CollectionChanged;
12             data.Add("One");
13             data.Add("Two");
14             data.Insert(1, "Three");
15             data.Remove("One");
16             
17         }
18 
19         static void Data_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
20         {
21             Console.WriteLine("action: {0}", e.Action.ToString());
22 
23             if (e.OldItems != null)     //有删除元素
24             {
25                 Console.WriteLine("starting index for old item(s): {0}", e.OldStartingIndex);
26                 Console.WriteLine("old item(s):");
27                 foreach (var item in e.OldItems)
28                 {
29                     Console.WriteLine(item);
30                 }
31             }
32             if (e.NewItems != null)     //有添加新元素
33             {
34                 Console.WriteLine("starting index for new item(s): {0}", e.NewStartingIndex);
35                 Console.WriteLine("new item(s): ");
36                 foreach (var item in e.NewItems)        
37                 {
38                     Console.WriteLine(item);
39                 }
40             }
41 
42             Console.WriteLine();
43 
44         }
45     }
46 }
可观察的集合
复制代码

 

十、位数组

  当我们着重处理一个以位为单位的数据时,就可以考虑用位数组。例如在很多 PCI 的 IO 卡中涉及很多的 I 位的读取以此判断各个开关量的状态,或者设置继电器的输出状态时,就会频繁用到位的操作。

  这种情况C#提供的BitArray和BitVector32类就会起到很大作用。

  BItArray类可以重新设置大小,BitVector32不能,它是固定32位的。

  BitArray类是一个引用类型,它包含一个 int 数组,每32位用一个新整数。比如63就是32+31,65就是32+32+1。

  BitArray常用方法:

复制代码
 1 public sealed class BitArray : ICollection, IEnumerable, ICloneable
 2 {
 3     public BitArray(BitArray bits); //用已有的BitArray给新的BitArray初始化
 4     
 5     public BitArray(bool[] values); //用布尔数组初始化
 6     
 7     public BitArray(byte[] bytes);  //用字节数组初始化
 8     
 9     public BitArray(int length);    //初始化并设置位数值,此值会在使用中自动增长
10     
11     public BitArray(int[] values);  //用int数组初始化
12     
13     public BitArray(int length, bool defaultValue); //初始化并设置默认值
14     
15     public int Count { get; }   //位数组中现存的位的个数
16     
17     public bool IsReadOnly { get; } //确定位数组是否只读
18 
19     public bool IsSynchronized { get; } //是否同步对此BitArray的操作,用在线程安全上
20     
21     public int Length { get; set; }   //位数组的位数
22 
23     public object SyncRoot { get; }
24     
25     public bool this[int index] { get; set; } //索引器,利用索引读位值
26     
27     public BitArray And(BitArray value);  //按位与
28 
29     public object Clone();  //创建BitArray 的浅表副本。
30     
31     public void CopyTo(Array array, int index);  //将BitArray拷贝到其他数组中
32     
33     public bool Get(int index);    //按下标读取位值
34 
35     public IEnumerator GetEnumerator(); //返回循环访问BitArray 的枚举数
36 
37     public BitArray Not();  //按位非
38 
39     public BitArray Or(BitArray value);  //按位或
40     
41     public void Set(int index, bool value);  //按位设置值
42     
43     public void SetAll(bool value); //设置所有位为指定值
44 
45     public BitArray Xor(BitArray value);  //按位异或
46 }
复制代码

  BitVector32固定长度32位,是一个结构,存储在栈上,效率较高。

  BitVector32常用方法:

复制代码
 1 namespace System.Collections.Specialized
 2 {
 3     public struct BitVector32    //首先它是是个结构,存储在栈上
 4     {
 5         public BitVector32(BitVector32 value); //用现有的来初始化
 6        
 7         public BitVector32(int data);  //以指定int值初始化
 8 
 9         public int Data { get; }  //返回当前BitVector32变量的值
10 
11         public int this[BitVector32.Section section] { get; set; }//以索引方式获取或设置指定片段上的值
12         
13         public bool this[int bit] { get; set; } //按照掩码来访问或设置值,注意bit是[掩码]不是[下标]
14         
15         public static int CreateMask();  //创建第一个屏蔽,即值为1
16         
17         public static int CreateMask(int previous); //按照指定值创建屏蔽
18 
19         public static BitVector32.Section CreateSection(short maxValue); //创建一个片段并指定最大值
20         
21         public static BitVector32.Section CreateSection(short maxValue, BitVector32.Section previous); //在指定的片段后面再创建一个片段并制定最大值。
22         
23         public override bool Equals(object o); //相等判断
24         
25         public override int GetHashCode(); //返回哈希代码
26         
27         public override string ToString(); //重写ToString()方法
28         
29         public static string ToString(BitVector32 value); //将位数组以字符串输出
30 
31         public struct Section  //BitVector32片段结构
32         {
33             public static bool operator !=(BitVector32.Section a, BitVector32.Section b); //重写!=操作符
34           
35             public static bool operator ==(BitVector32.Section a, BitVector32.Section b);  //重写==操作符
36 
37             public short Mask { get; }  //获取隔离掩码
38 
39             public short Offset { get; } //获取从 BitVector32 的起始处开始的此节的偏移量。
40 
41             public bool Equals(BitVector32.Section obj); //片段相等判断
42            
43             public override bool Equals(object o);  //相等判断
44            
45             public override int GetHashCode();
46             
47             public override string ToString();
48            
49             public static string ToString(BitVector32.Section value);
50         }
51     }
52 }
复制代码

  本节参考文章:BitArrayBItVector32

十一、并发集合

  待学习多线程后回来补充。。。

 

十二、性能

  有些功能类似的集合性能却相差甚远(na表示此操作不能进行)

 

 

    

 

posted @   xunzf  阅读(235)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示