Collection集合

一、 Collection集合

Collection集合是专门用于数据存储和检索的类, 有两种主要的集合类型:泛型集合和非泛型集合

构建泛型集合时,接受类型形参,并向改集合添加项或者从该集合删除项时,无需在Object 类型间来回转换;而非泛型集合将 项 存储为 Obejct, 需要强制转换

集合的基本类型

集合类型表示收集数据的不同方式,例如哈希表、队列、堆栈、包、字典和列表

所有集合都直接或间接基于 ICollection<T>ICollection 接口。 IListIDictionary 及其泛型对应项均派生自这两个接口

1. 在基于 IList或直接基于 ICollection的集合中,每个元素都只包含一个值。 这些类型包括:
2. 在基于 IDictionary接口的集合中,每个元素都只包含一个键和一个值。 这些类型包括:

各种集合类和用法

下面是各种常用的 System.Collection 命名空间的类

1. 动态数组(ArrayList): 单独索引的对象的有序集合
  • 可以使用索引在指定的位置添加和移动项目
  • 动态数组会自动重新调整大小
  • 允许在列表中进行动态内存分配、增加、搜索、排序各项
2. 哈希表(Hashtable): 使用键来访问集合中的元素
  • 识别键值
  • 哈希表中的每一项都有一个键值对
  • 键用于访问集合中的项目
3. 排序列表(SortedList): 使用键和索引来访问列表中的项
  • 排序列表是数组和哈希表的组合
  • 包含一个可使用键或索引访问各项的列表
  • 集合中的各项总是按键值排序
4. 堆栈(Stack): 后进先出的对象集合
  • 对各项进行后进先出的访问时,就可以使用堆栈
  • 往列表中添加一项为 推入 元素
  • 从列表中移除一项为 弹出 元素
5. 队列(Queue): 先进先出的对象集合
  • 对各项进行先进先出的访问,就可以使用队列
  • 往列表中添加一项为 入队
  • 从列表中移除一项为 出队
6. 点阵列(BitArray): 使用值1 和 0 来表示 二进制 数组
  • 需要存储位,但不知道位数,就可以使用点阵列
  • 使用整数索引从点阵列集合中访问各项
  • 索引从零开始

不同特征的集合类型

按照不同的特征对不同的集合进行分类

  • 元素访问:可以枚举每个集合以按顺序访问每个元素。 某些集合可通过索引(元素在有序集合中的位置)访问元素;其他集合可按 访问元素,其中 与单个 相关联
  • 性能配置文件:每个集合都有不同的性能配置文件,可用于添加元素、查找元素或移除元素等操作
  • 动态增长和收缩:大多数集合支持动态添加或移除元素。 需要注意的是,ArraySystem.Span<T>System.Memory<T> 不支持

备注:System.Span<T>属于 ref struct 类型,可提供一系列元素的快照,而无需复制这些元素

1. 可索引集合

可索引集合 是一个可以使用其索引访问每个元素的集合,索引是序列中在它之前的元素数,若按 索引0 引用的元素是第一个元素,索引1 则是第二个元素

创建和初始化字符串列表、移除元素并将元素添加到列表末尾,每次修改后,都使用 foreach 语句或 for 循环来循环访问字符串


// 1.
// Create a list of strings by using a
// collection initializer.
List<string> salmons = ["chinook", "coho", "pink", "sockeye"];

// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook coho pink sockeye

// Remove an element from the list by specifying
// the object.
salmons.Remove("coho");


// Iterate using the index:
for (var index = 0; index < salmons.Count; index++)
{
    Console.Write(salmons[index] + " ");
}
// Output: chinook pink sockeye

// Add the removed element
salmons.Add("coho");
// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook pink sockeye coho

// 2.
/*
从一个泛型列表中按索引移除元素
它使用以降序进行循环访问的 `for` 语句,而不是 `foreach` 语句
RemoveAt 方法将导致已移除元素后的元素索引值减小
*/

List<int> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

// Remove odd numbers.
for (var index = numbers.Count - 1; index >= 0; index--)
{
    if (numbers[index] % 2 == 1)
    {
        // Remove the element by specifying
        // the zero-based index in the list.
        numbers.RemoveAt(index);
    }
}

// Iterate through the list.
// A lambda expression is placed in the ForEach method
// of the List(T) object.
numbers.ForEach( number => Console.Write(number + " "));
// Output: 0 2 4 6 8

// 3.
/*‘
对于 List<T>中的元素类型,还可以定义自己的类
*/
private static void IterateThroughList()
{
    var theGalaxies = new List<Galaxy>
    {
        new (){ Name="Tadpole", MegaLightYears=400},
        new (){ Name="Pinwheel", MegaLightYears=25},
        new (){ Name="Milky Way", MegaLightYears=0},
        new (){ Name="Andromeda", MegaLightYears=3}
    };

    foreach (Galaxy theGalaxy in theGalaxies)
    {
        Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears);
    }

    // Output:
    //  Tadpole  400
    //  Pinwheel  25
    //  Milky Way  0
    //  Andromeda  3
}

public class Galaxy
{
    public string Name { get; set; }
    public int MegaLightYears { get; set; }
}
2. 键值对集合

字典集合,可通过使用每个元素的键访问集合中的元素,最常见的字典集合是 Dictionary<TKey,TValue> 类,每次对字典的添加都包含一个值和与其关联的键

  • Dictionary<TKey, TValue> 是一种常用的泛型集合类型,用于存储键值对

  • 基于哈希表实现,可以提供快速的键查找和检索功能

  • 字典中的键必须是唯一的,若尝试使用相同的键添加两次,会导致运行时异常

    • Dictionary<TKey, TValue> 的简单介绍以及基本用法
    // 1. 声明和初始化一个字典
    using System;
    using System.Collections.Generic;
    
    class Program
    {
        static void Main()
        {
            // 声明并初始化一个字符串键和整数值的字典
            Dictionary<string, int> ages = new Dictionary<string, int>();
    
            // 可以使用索引器(`[]`)来添加、修改或获取字典中的值
            // 添加键值对到字典
            ages["Alice"] = 28;
            ages["Bob"] = 30;
            ages["Charlie"] = 25;
    
            // 或者使用集合初始化器初始化字典
            Dictionary<string, string> colors = new Dictionary<string, string>()
            {
                { "Red", "#FF0000" },
                { "Green", "#00FF00" },
                { "Blue", "#0000FF" }
            };
    
            // 访问字典中的值
            Console.WriteLine("Alice's age is: " + ages["Alice"]);
            Console.WriteLine("The color code for Green is: " + colors["Green"]);
    
            // 使用 ContainsKey() 方法 检查字典中是否存在某个键
            if (ages.ContainsKey("Bob"))
            {
                Console.WriteLine("Bob's age is: " + ages["Bob"]);
            }
    
            // 使用 foreach 循环 遍历字典
            foreach (var pair in colors)
            {
                Console.WriteLine("Key: " + pair.Key + ", Value: " + pair.Value);
            }
        }
    }
    
    

    // 以下的示例稍微比较复杂

    private static void IterateThruDictionary()
    {
        Dictionary<string, Element> elements = BuildDictionary();
    
         // 使用 ContainsKey 方法和  Dictionary 的 Item[] 属性按键快速查找某个项
         // 使用 `Item` 属性可通过 C# 中的 `elements[symbol]` 来访问 `elements` 集合中的项
         if (elements.ContainsKey(symbol) == false)
         {
              Console.WriteLine(symbol + " not found");
          }
          else
          {
              Element theElement = elements[symbol];
              Console.WriteLine("found: " + theElement.Name);
          }
    
          // 使用 TryGetValue方法按键快速查找某个项
          if (elements.TryGetValue(symbol, out Element? theElement) == false)
                Console.WriteLine(symbol + " not found");
          else
                Console.WriteLine("found: " + theElement.Name);
    
    
        foreach (KeyValuePair<string, Element> kvp in elements)
        {
            Element theElement = kvp.Value;
    
            Console.WriteLine("key: " + kvp.Key);
            Console.WriteLine("values: " + theElement.Symbol + " " +
                theElement.Name + " " + theElement.AtomicNumber);
        }
    }
    
    public class Element
    {
        public required string Symbol { get; init; }
        public required string Name { get; init; }
        public required int AtomicNumber { get; init; }
    }
    
    private static Dictionary<string, Element> BuildDictionary() =>
        new ()
        {
            {"K",
                new (){ Symbol="K", Name="Potassium", AtomicNumber=19}},
            {"Ca",
                new (){ Symbol="Ca", Name="Calcium", AtomicNumber=20}},
            {"Sc",
                new (){ Symbol="Sc", Name="Scandium", AtomicNumber=21}},
            {"Ti",
                new (){ Symbol="Ti", Name="Titanium", AtomicNumber=22}}
        };
    

ps :

3.迭代器

迭代器可以是一种方法,或是一个 get 访问器,用于对集合执行自定义迭代,使用 yield return 语句返回集合的每一个元素,每次返回一个元素

通过使用 foreach 语句调用迭代器,foreach 循环的每次迭代都会调用迭代器, 迭代器中到达 yield return 语句时,会返回一个表达式,并保留当前在代码中的位置。 下次调用迭代器时,将从该位置重新开始执行。

image.png
4. LINQ 和集合

可以使用语言集成查询 (LINQ) 来访问集合, LINQ 查询提供筛选、排序和分组功能

private static void ShowLINQ()
{
    List<Element> elements = BuildList();

    // LINQ Query.
    var subset = from theElement in elements
                 where theElement.AtomicNumber < 22
                 orderby theElement.Name
                 select theElement;

    foreach (Element theElement in subset)
    {
        Console.WriteLine(theElement.Name + " " + theElement.AtomicNumber);
    }

    // Output:
    //  Calcium 20
    //  Potassium 19
    //  Scandium 21
}

private static List<Element> BuildList() => new()
    {
        { new(){ Symbol="K", Name="Potassium", AtomicNumber=19}},
        { new(){ Symbol="Ca", Name="Calcium", AtomicNumber=20}},
        { new(){ Symbol="Sc", Name="Scandium", AtomicNumber=21}},
        { new(){ Symbol="Ti", Name="Titanium", AtomicNumber=22}}
    };

常用的集合功能

所有集合都提供用于在集合中添加、删除或查找项的方法;此外,所有直接或间接实现 ICollection 接口或 ICollection<T> 接口的集合均共享这些功能:

1. 可枚举集合
  • 启用要循环访问的集合,.net可通过实现System.Collections.IEnumerableSystem.Collections.Generic.IEnumerable<T>的接口
  • 可将枚举器看作集合中可指向任何元素的可移动指针
  • foreach, in语句和 For Each...Next语句可以使用 GetEnumerator 方法公开的枚举器并隐藏操作枚举器的复杂性
  • 任何 实现了System.Collections.Generic.IEnumerable<T>的集合均被认为是可查询类型,并可使用 LINQ 对其进行查询
2. 可建集合内容复制到数组
  • 使用 CopyTo 方法将所有集合复制到数组中;但新数组中的元素顺序是以枚举器返回元素的顺序为依据,得到的数组始终是一维数组,下限为零
3. 容量和计数属性
  • 集合的容量是它可包含的元素数;集合的计数是它实际所含的元素数
  • 当集合到达当前容量时,大多数集合会知道扩容,重新分配内存并将元素从旧集合复制到新集合
    • 避免因多次重新分配而导致的性能较差的最佳方式是:将初始容量设置为集合的估计大小
    • BitArray 是一种特殊的情况:它的容量与其长度相同,而其长度与其计数相同
    • 例: 对List<T>,如果Count 比 Capacity 少,添加项就是一项O(1)操作;若需添加容量以容纳新元素,则添加项成为O(n)操作,其中 n 是 Count
4. 下限一致
  • 集合的下限是其第一个元素的索引, System.Collections 命名空间中的所有索引集合的下限均为零,这表示它们从 0 开始建立索引
  • Array数组默认下限为零,但使用 Array.CreateInstance 创建 Array 类的实例时可定义其他下限
5. 同步以多个线程进行访问(仅System.Collection类)

System.Collections命名空间中的非泛型集合类型通过同步提供一些线程安全性;通常通过 SyncRootIsSynchronized 成员公开

选择集合类

1. 顺序列表访问(检索元素值后是是否丢弃元素)
2. 特定顺序访问
  • 先进先出 (FIFO) : Queue 类以及 Queue<T>、ConcurrentQueue<T>和 ImmutableQueue<T>泛型类
  • 后进先出 (LIFO) : Stack 类以及 Stack<T>、ConcurrentStack<T> 和 ImmutableStack<T> 泛型类
  • 从开头到末尾 / 从末尾到开头:LinkedList<T> 泛型类
3. 按索引访问
4. 每个元素包含(值、键)的组合
6. 与输入方式不同的方式排序
7. 快速搜索和信息检索
  • 小集合
    • ListDictionary速度比 Hashtable 快
    • Dictionary<TKey,TValue> 泛型类提供比 SortedDictionary<TKey,TValue> 泛型类更快的查找
  • 多线程 : ConcurrentDictionary<TKey,TValue>
8. 只接受字符串的集合

如何选择一个集合类:https://learn.microsoft.com/zh-cn/dotnet/standard/collections/selecting-a-collection-class

使用泛型集合的情况

使用泛型集合:可获得类型安全的自动化优点而无需从基集合类型派生和实现特定类型的成员

当集合元素为值类型时,泛型集合类型也通常优于与对应的非泛型集合类型,因为使用泛型不用对元素进行装箱

1. 泛型类型对应于现有的集合类型
2. 泛型类型没有对应的非泛型集合类型
不可变生成器

所有不可变的集合类型都提供 Builder 类,可以通过调用非泛型 CreateBuilder() 方法来创建 Builder 对象

通过 Builder 实例,可以调用 ToImmutable(),同样,通过 Immutable集合中,可以调用 ToBuilder() 从泛型不可变集合创建生成器实例,以下是各种 Builder 类型:

集合内的比较和排序

集合通常使用相等比较器和/或排序比较器

1. 检查元素是否相等

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

  • 如果类型 T 实现 IEquatable<T> 泛型接口,则相等比较器是该接口的Equals 方法
  • 如果类型 T 未实现 IEquatable<T>,则使用Object.Equals
2. 确定排序顺序

BinarySearch 和 Sort 等方法将排序比较器用于集合元素,可在集合的元素间进行比较,或在元素或指定值之间进行比较

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

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

ps:

System.Collections 命名空间:https://learn.microsoft.com/zh-cn/dotnet/api/system.collections?view=net-8.0

集合和数据结构:https://learn.microsoft.com/zh-cn/dotnet/standard/collections/

常用的集合类型: https://learn.microsoft.com/zh-cn/dotnet/standard/collections/commonly-used-collection-types?source=recommendations

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容