当前位置:   article > 正文

【Unity&C#】集合数据结构都有哪些?

【Unity&C#】集合数据结构都有哪些?

本文主要讨论:List(列表),Dictionary(字典) ,Queue(队列) ,Stack(栈),HashSet(唯一集合),LinkedList(双向链表),BitArray(位数组),SortedSet(排序集合),ObservableCollection(可观察到的集合(机翻),我喜欢称为可观集合 ), Queue(队列),ConcurrentDictionary<K, V>(多线程字典(机翻是并发字典 )),Immutable Collections( 不可变的集合),Custom Collections(自定义集合)。

什么是C# 集合数据结构?Array呢?

   从C#的角度来看,Array不属于集合(Collections)类别。在C#中,集合类通常指的是实现了泛型集合接口(例如 ICollection<T>)的可变大小的数据结构,如List<T>, Dictionary<K, V>, HashSet<T>, Queue<T>, Stack<T>, LinkedList<T> 等。

    集合类指.NET Framework 中的静态类 System.Collections,它包含了一系列用于处理和操作集合的类和接口。

    与集合类不同,Array 是一种不可变大小的数组数据结构,元素类型相同,具有固定的长度。虽然数组在C#中非常常见,而且具有高效的元素访问速度,但它们在大小上是固定的,不支持动态添加或删除元素,因此在概念上不被视为集合类。

    尽管如此,在实际编程中,数组(Array)和集合(Collections)都用于存储和管理数据,开发者根据具体需求选择使用哪种数据结构,以满足他们的编程目标。

    C# 提供了丰富的集合数据结构,每种都具有不同的特点和用途,最重要的是性能和复杂度等。以下是一些常见的 C# 集合数据结构以及它们的特点:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

List:

特点:动态大小的列表,可存储任何类型的元素,支持索引访问。
适用于:动态数组,用于存储和管理元素列表。

List<int> numbers = new List<int>();     
 // 添加元素
 numbers.Add(1);
 numbers.Add(2);
 numbers.Add(3);      
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Dictionary<K, V>:

特点:键值对存储结构,快速查找和存储数据,键唯一。
适用于:存储和管理键值对,例如配置、映射、缓存。

Dictionary<string, int> dictionary = new Dictionary<string, int>();
dictionary.Add("apple", 1);
dictionary.Add("banana", 2);

  • 1
  • 2
  • 3
  • 4

Queue 和 Stack:

特点:队列和栈数据结构,支持先进先出 (FIFO) 和后进先出 (LIFO) 操作。
适用于:任务排队、处理消息、遍历数据。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Queue<string> tasks = new Queue<string>();
        
        // 入队
        tasks.Enqueue("Task 1");
        tasks.Enqueue("Task 2");
        tasks.Enqueue("Task 3");

        // 出队
        while (tasks.Count > 0)
        {
            string task = tasks.Dequeue();
            Console.WriteLine("Processing: " + task);
        }
        ---------------------------
        --------------------------- 
        Stack<int> numbers = new Stack<int>();
        
        // 压栈
        numbers.Push(1);
        numbers.Push(2);
        numbers.Push(3);

        // 弹栈
        while (numbers.Count > 0)
        {
            int number = numbers.Pop();
            Console.WriteLine("Popped: " + number);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

LinkedList:

特点:双向链表,用于频繁插入和删除操作,不适用于随机访问。
适用于:频繁插入和删除的场景,如某些算法。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        LinkedList<string> names = new LinkedList<string>();
        
        // 添加元素
        names.AddLast("Alice");
        names.AddLast("Bob");
        names.AddLast("Charlie");

        // 遍历链表
        Console.WriteLine("LinkedList Elements:");
        foreach (string name in names)
        {
            Console.WriteLine(name);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

BitArray:

特点:位数组,用于处理位级别的操作。
适用于:处理大量布尔值的情况,如位图索引。

using System;
using System.Collections;

class Program
{
    static void Main()
    {
        // 创建一个 BitArray,初始大小为 8
        BitArray bits = new BitArray(8);

        // 设置一些位
        bits.Set(0, true);
        bits.Set(1, false);
        bits.Set(2, true);

        // 输出所有位
        for (int i = 0; i < bits.Length; i++)
        {
            Console.WriteLine($"Bit {i}: {bits[i]}");
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

HashSet:

无序集合,用于存储不重复的元素,基于哈希表实现。
适用场景:需要快速查找、添加、删除元素,且元素
确保元素的唯一性,适用于去重操作。

HashSet<int> hashSet = new HashSet<int>();
hashSet.Add(1);
hashSet.Add(2);
hashSet.Add(3);
  • 1
  • 2
  • 3
  • 4

SortedSet:

特点:有序集合,自动排序元素。
适用于:需要元素有序的场景。

SortedSet<int> sortedSet = new SortedSet<int>();
sortedSet.Add(3);
sortedSet.Add(1);
sortedSet.Add(2);
  • 1
  • 2
  • 3
  • 4

ObservableCollection:

特点:可观察的集合,用于绑定到界面元素并在数据更改时通知

1.数据变更通知:当集合中的项目被添加、删除或整个集合被刷新时,它会触发 CollectionChanged 事件。
2.自动更新UI:在数据绑定的场景中,UI会自动响应集合的变化,反映最新的数据。
3.线程安全性:需要注意的是,ObservableCollection<T> 本身不是线程安全的。如果在多线程环境中操作,需要额外的同步机制。。
  • 1
  • 2
  • 3

适用于:适用于数据绑定,特别是在UI编程中或者WPF 以及其他 XAML 应用的数据绑定。

1.MVVM模式中的数据绑定:在 WPF、Xamarin 或其他使用 MVVM 模式的应用程序中,当模型或视图模型的数据发生变化需要自动更新到UI界面时。
2.需要监视集合更改的场景:在任何需要知道集合何时被修改的情况下
  • 1
  • 2
using System;
using System.Collections.ObjectModel;

public class MainClass
{
    public static void Main()
    {
        // 创建一个 ObservableCollection
        ObservableCollection<string> names = new ObservableCollection<string>();

        // 添加事件处理器
        names.CollectionChanged += Names_CollectionChanged;

        // 向集合中添加元素
        names.Add("John");
        names.Add("Jane");
    }

    private static void Names_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        // 当集合改变时,这里的代码会被执行
        Console.WriteLine("Collection changed: " + e.Action.ToString());
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

Queue无返回事件:

特点:存储委托或函数,可用于实现事件队列。
适用于:处理事件队列,例如动画序列。

Queue Action是 C# 中基于 System.Collections.Generic.Queue 类的一个特殊用法,用于存储 Action 类型的对象,即无返回值的委托。它按照先进先出(FIFO)的顺序管理这些委托。
先进先出:元素按照它们被添加的顺序出队。
用于委托管理:专门用于存储和管理 Action 委托。
线程安全性:标准的 Queue 不是线程安全的,需要额外机制来处理多线程
适用于任务需要按顺序执行的场景,如事件处理或任务调度。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Queue<Action> queue = new Queue<Action>();

        // 添加任务
        queue.Enqueue(() => Console.WriteLine("First Task"));
        queue.Enqueue(() => Console.WriteLine("Second Task"));

        // 执行任务
        while (queue.Count > 0)
        {
            Action task = queue.Dequeue();
            task();
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

ConcurrentDictionary<K, V>:

特点:多线程安全的字典,支持并发操作。
适用于:多线程环境下的键值对存储和操作。

ConcurrentDictionary<K, V> 是 System.Collections.Concurrent 命名空间中的一个线程安全的字典集合。
线程安全:可在多线程环境下安全使用。
高性能:为并发操作优化,比使用锁定的标准 Dictionary 效率更高。
原子操作:提供原子级别的添加、移除等操作。
多线程数据共享:在多线程应用中共享和修改键值对。

using System;
using System.Collections.Concurrent;

class Program
{
    static void Main()
    {
        ConcurrentDictionary<string, int> concurrentDictionary = new ConcurrentDictionary<string, int>();

        // 添加元素
        concurrentDictionary.TryAdd("key1", 1);
        concurrentDictionary.TryAdd("key2", 2);

        // 访问元素
        Console.WriteLine(concurrentDictionary["key1"]);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Immutable Collections:

特点:不可变的集合,一旦创建就无法修改,线程安全。
适用于:需要保持不变性的情况,函数式编程。

不可变集合是 System.Collections.Immutable 命名空间中的一系列集合类,一旦创建就不能更改。
不可变性:集合创建后,其内容不可更改。
线程安全:由于不可变,天然线程安全。
副作用减少:不会因修改集合而影响其他引用该集合的代码。
适用场景
并发编程:在多线程环境中共享集合数据。
函数式编程风格:避免副作用和可变状态。

using System;
using System.Collections.Immutable;

class Program
{
    static void Main()
    {
        var immutableList = ImmutableList.Create<int>();
        immutableList = immutableList.Add(1);
        immutableList = immutableList.Add(2);

        foreach (var item in immutableList)
        {
            Console.WriteLine(item);
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Custom Collections:

灵活性:可以根据需求定制集合的行为。
功能定制:可以实现特定的数据结构和算法。
适用场景
特殊需求:标准库中的集合不满足特定需求时。
优化性能:针对特定场景优化性能。

///假设我们要实现一个简单的自定义循环队列:
using System;
using System.Collections;
using System.Collections.Generic;

public class CircularQueue<T> : IEnumerable<T>
{
    private T[] _items;
    private int _head;
    private int _tail;

    public CircularQueue(int capacity)
    {
        _items = new T[capacity];
        _head = -1;
        _tail = -1;
    }

    public void Enqueue(T item)
    {
        // 实现循环队列的入队逻辑
    }

    public T Dequeue()
    {
        // 实现循环队列的出队逻辑
    }

    public IEnumerator<T> GetEnumerator()
    {
        // 实现循环队列的迭代器
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

作者的话

还有很多比较特殊的集合类,但是本文所列的这些在传统集合具有代表性,供各位参考,代码示例比较简单,以后有时间会完善。

(本文部分内容参考自《C#本质论8.0》Mark Michaelis)

关键名词说明

原子操作

提供原子级别的添加、移除等操作。

原子操作是指在多线程编程中,一个操作在开始到结束的过程中不会被其他线程中断的操作。
在多线程环境当我们说 ConcurrentDictionary<K, V> 提供了原子级别的添加、移除等操作时,我们是指:中,原子操作非常重要,因为它们帮助避免竞态条件,确保数据的一致性和完整性。

  1. 原子级添加和移除:
    例如,当你使用 TryAdd 方法向 ConcurrentDictionary 添加一个键值对时,整个添加过程是不可分割的。不会有两个线程同时添加同一个键,并且在任何时间点,其他线程看到的字典都是一致的状态。
    同样地,使用 TryRemove 移除元素时,整个移除过程也是原子的。

  2. 线程安全性:
    这意味着在多线程环境中,即使多个线程同时尝试添加或移除字典中的元素,ConcurrentDictionary 也会确保每个操作都是完整执行的,不会相互干扰,也不会导致数据不一致的问题。

  3. 无需外部同步:
    在使用普通的 Dictionary<K, V> 时,你通常需要使用锁(如 lock 语句)来确保添加或移除操作的线程安全。但在 ConcurrentDictionary 中,这种同步机制已经内置,无需额外的锁定。

示例如下

using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        var dict = new ConcurrentDictionary<int, string>();

        // 同时启动多个任务添加元素
        Parallel.For(0, 1000, (i) =>
        {
            dict.TryAdd(i, $"Value{i}");
        });

        // 此时,dict 中应该恰好有 1000 个元素
        Console.WriteLine($"Number of items: {dict.Count}");
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

函数式编程

1.不可变性:数据对象在被创建后不允许被修改,而是通过创建新的对象来表示数据的变化。
2.纯函数:函数的输出仅依赖于输入,并且不产生副作用(如修改全局状态或输入参数)。
3.高阶函数:函数可以作为参数传递给其他函数,或者作为结果返回。
4.递归:倾向于使用递归而非循环来实现重复操作。

与其他编程风格的比较
命令式编程:
关注如何实现(步骤和过程)。
示例:使用循环和条件语句来操作变量。

List<int> doubled = new List<int>();
foreach (var number in numbers)
{
    doubled.Add(number * 2);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

面向对象编程:

将数据和行为封装在对象中。
示例:使用类和对象来组织代码,强调继承、封装和多态。(这个我就不多说了,相信学习C#各位已经掌握的炉火纯青)

函数式编程:
关注做什么(描述问题的解决方案)。
示例:使用不可变数据和函数组合。

// 函数式风格在C#中,函数式编程风格的示例可能是使用LINQ表达式来处理集合,而不是使用传统的循环:
var doubled = numbers.Select(number => number * 2);
  • 1
  • 2

The end

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/114515?site
推荐阅读
相关标签
  

闽ICP备14008679号