C# 容器
C# 容器一、List<>二、LinkedList<>三、Dictionary<>四、HashTable五、Queue六、Stack七、HashSet<>一、List<>泛型容器,与C++的vector类似,是顺序结构而不是链式结构。二、LinkedList<>C#中的泛型链表,与C++的list类似,是链式结构。三、Dict
C# 容器
一、Array 数组
数组是一个固定大小的集合,用于存储相同类型的元素。数组的大小在创建时确定,无法动态改变。
二、List<T
> 动态数组类型
List 是一个常用的动态数组,它可以根据需要自动扩展和缩小。List 的底层实现是基于数组,它使用数组来存储元素,并提供了一系列方法来管理这些元素。
List<
T
> 的一些主要特点和底层实现的细节
- 动态数组: List<
T
> 使用一个数组来存储元素。初始时,该数组的大小是固定的,但在添加元素时,如果数组已满,List<T
> 会自动分配一个更大的数组,并将元素从旧数组复制到新数组中。- 自动调整大小: 当数组不足以容纳新元素时,List<
T
> 会根据需要自动调整数组的大小。这样,你就不需要手动管理数组大小,而是可以专注于添加和删除元素。- 元素顺序: List<
T
> 中的元素是按照添加的顺序进行存储的。这意味着你可以按照索引访问元素,并且它们的顺序会与你添加它们的顺序相同。- 常用操作: List<
T
> 提供了许多常用的操作,如添加元素、删除元素、插入元素、查找元素等。这些操作都是基于数组的底层实现来实现的。- 容量和实际元素数: List<
T
> 维护了两个重要的属性:容量(Capacity)和实际元素数(Count)。容量是内部数组的大小,而实际元素数是当前列表中包含的元素数量。当实际元素数达到容量时,List<T
> 会自动分配一个更大的数组。- 性能: List<
T
> 的添加和访问操作的性能通常很好,因为它们是基于数组的实现。然而,插入和删除操作可能涉及元素的移动,可能会影响性能,尤其是在大列表中。
虽然我们常将 List<
T
> 看作是存储对象的容器,但其实它存储的并不是对象本身,而是对象的引用。
//声明一个List泛型集合的变量listNew
List<string> listNew=new List<string>();
1.Add()方法,添加元素的方法
2.Clear()方法,无返回值,清空集合中的所有元素
3.Contains()方法,返回布尔型数据,参数为集合中元素的数据类型
4.Equals()方法
5.Indexof()返回值为int,从索引位置0开始查找元素,并得到索引值
6.Insert()方法,插入元素
listNew.Insert(3,"三点五号元素");
7.Remove()方法,删除指定元素
8.RemoveAt()方法,根据索引位置删除元素
9.Reserve()方法,将集合中的所有元素反向排序
10.ToArray()方法,将集合转换为数组
三、LinkedList<T
> 双向链表
一个双向链表,用于存储相同类型的元素。它支持高效的插入和删除操作。
LinkedList<string> months = new LinkedList<string>();
months.AddLast("March");
months.AddFirst("January");
var march = months.Find("March");
months.AddBefore(march, "February");
months.AddAfter(march, "April");
四、Dictionary<TKey, TValue> 字典
字典是一个键-值对的集合,用于存储和检索具有唯一键的元素。每个键映射到一个值。
//定义
Dictionary<string, string> openWith = new Dictionary<string, string>();
//添加元素
openWith.Add("txt", "notepad.exe");
//取值
Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]);
//更改值
openWith["rtf"] = "winword.exe";
//遍历key
foreach (string key in openWith.Keys)
{
Console.WriteLine("Key = {0}", key);
}
//遍历字典
foreach (KeyValuePair<string, string> kvp in openWith)
{
Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value);
}
//删除元素
openWith.Remove("doc");
常用属性
名称 说明
Comparer 获取用于确定字典中的键是否相等的 IEqualityComparer<T>。
Count 获取包含在 Dictionary<TKey, TValue> 中的键/值对的数目。
Item 获取或设置与指定的键相关联的值。
Keys 获取包含 Dictionary<TKey, TValue> 中的键的集合。
Values 获取包含 Dictionary<TKey, TValue> 中的值的集合。
常用方法
名称 说明
Add 将指定的键和值添加到字典中。
Clear 从 Dictionary<TKey, TValue> 中移除所有的键和值。
ContainsKey 确定 Dictionary<TKey, TValue> 是否包含指定的键。
ContainsValue 确定 Dictionary<TKey, TValue> 是否包含特定值。
Equals(Object) 确定指定的 Object 是否等于当前的 Object。 (继承自 Object。)
Finalize 允许对象在“垃圾回收”回收之前尝试释放资源并执行其他清理操作。 (继承自 Object。)
GetEnumerator 返回循环访问 Dictionary<TKey, TValue> 的枚举器。
GetHashCode 用作特定类型的哈希函数。 (继承自 Object。)
GetObjectData 实现 System.Runtime.Serialization.ISerializable 接口,并返回序列化 Dictionary<TKey, TValue> 实例所需的数据。
GetType 获取当前实例的 Type。 (继承自 Object。)
MemberwiseClone 创建当前 Object 的浅表副本。 (继承自 Object。)
OnDeserialization 实现 System.Runtime.Serialization.ISerializable 接口,并在完成反序列化之后引发反序列化事件。
Remove 从 Dictionary<TKey, TValue> 中移除所指定的键的值。
ToString 返回表示当前对象的字符串。 (继承自 Object。)
TryGetValue 获取与指定的键相关联的值。
五、Queue<T
>队列
用于实现先进先出(FIFO)的数据结构。它支持在队尾添加元素,在队头移除元素。
Enqueue():在队列的末端添加元素
Dequeue():在队列的头部读取和删除一个元素,注意,这里读取元素的同时也删除了这个元素。如果队列中不再有任何元素。就抛出异常
Peek():在队列的头读取一个元素,但是不删除它
Count:返回队列中的元素个数
TrimExcess():重新设置队列的容量,因为调用Dequeue方法读取删除元素后不会重新设置队列的容量。
Contains():确定某个元素是否在队列中
CopyTo():把元素队列复制到一个已有的数组中
ToArray():返回一个包含元素的新数组
六、Stack<T
> 栈
用于实现后进先出(LIFO)的数据结构。它支持在栈顶添加元素和移除元素。可使用泛型也可以不使用。不使用泛型时,可利用Stack .Synchronized(new Stack ())使多线程安全。
//定义
private Stack<string> StaArray = new Stack<string>();
//压栈
StaArray.Push("张三");
//查栈
StaArray.Peek()
//出栈
StaArray.Pop()
七、HashSet<T
> 集合
C#的hash集,是泛型,非线性结构.用于存储唯一的元素,不允许重复。它支持高效的元素查找和去重。
- 哈希表(Hash Table): HashSet 使用哈希表作为底层数据结构。哈希表是一种用于快速查找和插入的数据结构,它将键映射到值的过程通过哈希函数来完成。
- 哈希函数: 哈希函数是一个将元素映射到哈希值的函数。在 HashSet 中,哈希函数将元素转换为一个索引,以便快速定位元素。好的哈希函数能够尽可能避免冲突,即不同的元素映射到相同的索引。
- 桶(Buckets): 哈希表通常由一组桶组成,每个桶存储一组哈希值相同(或相近)的元素。HashSet 中的每个桶可以包含一个或多个元素。
- 碰撞解决: 由于不同的元素可能映射到相同的哈希值,可能会导致冲突。哈希表使用不同的碰撞解决策略来处理这些冲突,常见的有链地址法(Chaining)和开放寻址法(Open Addressing)等。
- 动态大小: HashSet 的哈希表在内部会动态地调整大小,以适应添加的元素数量。当哈希表的负载因子(即元素数量与桶数量的比例)超过阈值时,会自动重新调整桶的数量。
- 性能: 哈希表的平均查找和插入操作的时间复杂度通常是 O(1)(常数时间),但在极端情况下可能会退化为 O(n)(线性时间)。
Add(T item): 将指定的元素添加到集合中。如果元素已存在,不会添加,并返回 false。
Remove(T item): 从集合中移除指定的元素。如果元素存在并成功移除,返回 true,否则返回 false。
Contains(T item): 判断集合是否包含指定的元素。如果包含,返回 true,否则返回 false。
Clear(): 清空集合中的所有元素,使集合变为空集。
Count: 获取集合中元素的数量。
UnionWith(IEnumerable<T> other): 修改当前集合,使其包含当前集合和另一个集合的并集。
IntersectWith(IEnumerable<T> other): 修改当前集合,使其包含当前集合和另一个集合的交集。
ExceptWith(IEnumerable<T> other): 修改当前集合,移除当前集合中与另一个集合相同的元素。
IsSubsetOf(IEnumerable<T> other): 判断当前集合是否是另一个集合的子集。
IsSupersetOf(IEnumerable<T> other): 判断当前集合是否是另一个集合的超集。
IsProperSubsetOf(IEnumerable<T> other): 判断当前集合是否是另一个集合的真子集。
IsProperSupersetOf(IEnumerable<T> other): 判断当前集合是否是另一个集合的真超集。
SetEquals(IEnumerable<T> other): 判断当前集合是否与另一个集合相等(包含相同的元素,不考虑顺序)。
GetEnumerator(): 返回一个用于遍历集合的枚举器。
ToArray(): 将集合的元素复制到一个数组中。
八、SortedDictionary<TKey, TValue>有序的键-值对集合
一个有序的键-值对集合,根据键的排序顺序进行存储。
九、SortedSet有序的集合
一个有序的集合,存储唯一元素,并根据元素的排序顺序进行存储。
十、ConcurrentDictionary<TKey, TValue> 线程安全的字典
ConcurrentDictionary<TKey, TValue> 是一个线程安全的字典,支持并发操作。
ConcurrentDictionary<TKey, TValue> 实现线程安全的原理涉及多个方面,包括锁机制、细粒度的同步和数据分区等。以下是一些主要的原理和机制:
- 分段锁: ConcurrentDictionary<TKey, TValue> 内部使用了一种分段锁(Segmented Locking)的机制。它将整个字典分成多个段(segments),每个段都有自己的锁。这样,不同的段可以被不同的线程同时访问,从而提高了并发性能。
ConcurrentDictionary<TKey, TValue> 使用的分段锁是一种特殊的锁,称为 Stripe 锁。每个段内部都包含一个 Stripe 锁,用于保护该段内的数据访问。
Stripe 锁实际上是一种自旋锁(Spin Lock)的变种。自旋锁是一种不阻塞线程,而是通过不断尝试获取锁的方式来等待的锁。Stripe 锁在实现上通常会使用原子操作,如 CAS(Compare-and-Swap),来实现快速的锁定和释放操作。
每个段内的 Stripe 锁是独立的,不会影响其他段内的锁。这意味着多个线程可以在不同的段内并发地访问数据,而不会受到其他段锁的影响。这种设计在高并发情况下,允许多个线程同时进行操作,从而提高了并发性能。
- 锁粒度: 分段锁的设计允许多个线程同时访问不同的段,从而实现了更细粒度的锁定。相对于传统的全局锁,这种锁的粒度更小,减少了竞争和阻塞,提高了并发性。
- 无锁操作: ConcurrentDictionary<TKey, TValue> 的设计尽量避免使用全局锁,而是采用无锁或低锁的操作。例如,TryAdd 和 TryUpdate 方法使用了 CAS(Compare-and-Swap)等无锁算法,从而允许多个线程同时尝试修改数据。
- 分区数据: 内部的分段锁机制将数据分区到不同的段中,每个段都有自己的锁。这使得每个段可以被不同的线程同时访问,而不会影响到其他段的访问。这种分区减少了锁冲突,提高了并发性。
- 细粒度同步: ConcurrentDictionary<TKey, TValue> 在内部对不同的段进行同步,而不是对整个字典进行锁定。这样,多个线程可以同时访问不同的段,从而减少了阻塞和竞争。
- 逐出策略: ConcurrentDictionary<TKey, TValue> 使用逐出策略来腾出空间,以保持字典的大小。这也需要考虑并发性,以避免多个线程之间的冲突。
添加和获取元素:
using System.Collections.Concurrent;
ConcurrentDictionary<string, int> dict = new ConcurrentDictionary<string, int>();
dict.TryAdd("one", 1);
dict.TryAdd("two", 2);
if (dict.TryGetValue("one", out int value))
{
Console.WriteLine($"Value of 'one': {value}"); // Output: Value of 'one': 1
}
更新元素:
if (dict.TryGetValue("one", out int oldValue))
{
int newValue = oldValue + 1;
dict.TryUpdate("one", newValue, oldValue);
}
移除元素:
dict.TryRemove("two", out int removedValue);
循环遍历元素:
foreach (var kvp in dict)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
使用委托更新或添加元素:
dict.AddOrUpdate("three", 3, (key, oldValue) => oldValue + 1);
使用并发操作进行处理:
Parallel.ForEach(dict, kvp =>
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
});
并发处理数据:
void ProcessData(string key)
{
int newValue = ...; // compute new value
dict.AddOrUpdate(key, newValue, (k, oldValue) => newValue);
}
Parallel.For(0, 10, i =>
{
string key = $"key{i}";
ProcessData(key);
});
使用并发处理线程安全性:
bool keyExists = dict.ContainsKey("one");
十一、ConcurrentQueue线程安全的队列
ConcurrentQueue<
T
> 是一个线程安全的队列,支持并发操作。
- 分段锁: 所有这些集合都采用了分段锁的机制,将数据分成多个段,每个段都有自己的锁。这样,不同的线程可以同时访问不同的段,提高了并发性。
- 无锁操作: 它们在实现时尽量避免使用传统的全局锁,而是采用无锁或低锁的操作,如 CAS(Compare-and-Swap)等。
- 高并发性: 分段锁的设计使得多个线程可以在并发环境中安全地对集合进行读取、插入、更新和删除操作,从而提高了并发性能。
十二、ConcurrentStack线程安全的栈
ConcurrentStack 是一个线程安全的栈,支持并发操作。
- 分段锁: 所有这些集合都采用了分段锁的机制,将数据分成多个段,每个段都有自己的锁。这样,不同的线程可以同时访问不同的段,提高了并发性。
- 无锁操作: 它们在实现时尽量避免使用传统的全局锁,而是采用无锁或低锁的操作,如 CAS(Compare-and-Swap)等。
- 高并发性: 分段锁的设计使得多个线程可以在并发环境中安全地对集合进行读取、插入、更新和删除操作,从而提高了并发性能。
更多推荐
所有评论(0)