C#基础13-泛型集合

零、文章目录

C#基础13-泛型集合

1、List(动态数组)

(1)核心特性与优势
  • 类型安全
    • 强制元素类型一致(如 List<int> 只存整数),避免 ArrayList 的装箱拆箱开销。
    • 编译时类型检查,减少运行时错误。
  • 动态扩容
    • 初始容量默认为 4,添加元素超限时自动倍增容量(如 4→8→16)。
    • 可通过 Capacity 属性查看当前容量,Count 获取实际元素数。
  • 高效内存管理
    • 连续内存存储,支持快速索引访问(时间复杂度 O(1))。
    • 对比数组:无需手动扩容,对比 ArrayList:避免值类型装箱。
(2)基础操作(增删查改)
  • 初始化与添加元素
// 创建空列表 
List<string> names = new List<string>();  
 
// 添加单个元素 
names.Add("Alice");  
 
// 添加集合 
string[] newNames = { "Bob", "Charlie" };
names.AddRange(newNames);  
 
// 插入元素到指定位置 
names.Insert(1, "David");  // 索引1位置插入 
  • 删除元素
names.Remove("Alice");     // 删除首个匹配项 
names.RemoveAt(0);         // 删除索引0的元素 
names.RemoveRange(1, 2);   // 从索引1开始删2个元素 
names.Clear();             // 清空列表 
  • 查询与判断
bool hasBob = names.Contains("Bob");  // 检查存在性 
int index = names.IndexOf("Charlie"); // 获取首个匹配索引 
string first = names[0];              // 通过索引访问 
(3)进阶操作与高阶方法
  • 批量筛选与转换
    • FindAll + 谓词:返回满足条件的所有元素
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
List<int> evens = numbers.FindAll(n => n % 2 == 0);  // [2, 4]
- `ConvertAll`:类型转换(如 `int` → `string`)  
List<string> numStrings = numbers.ConvertAll(n => n.ToString());
  • 委托与 Lambda 应用
// 查找首个大于3的元素 
int target = numbers.Find(n => n > 3);  // 4 

// 检查所有元素是否满足条件 
bool allPositive = numbers.TrueForAll(n => n > 0);  // true 
  • 排序与反转
numbers.Sort();                      // 默认升序 [1,2,3,4,5]
numbers.Reverse();                   // 反转 [5,4,3,2,1]
numbers.Sort((a, b) => b.CompareTo(a)); // 自定义降序 
(4)性能优化与避坑指南
  • 预设容量:提前设置 Capacity 避免频繁扩容:
List<int> bigList = new List<int>(1000);  // 初始容量1000 
  • 避免遍历中修改集合:使用 for 循环替代 foreach 删除元素,防止 InvalidOperationException
  • 值类型集合优化:对大量结构体(struct)使用 List<ValueType> 减少堆内存分配。
  • 批量操作优先:用 AddRange() 替代多次 Add(),减少扩容次数。
(5)典型应用场景
  • 数据绑定:作为 DataGridView 数据源:
dataGridView.DataSource = new BindingList<Person>(personList);
  • API 数据处理:反序列化 JSON 数组 → List<T>
List<User> users = JsonConvert.DeserializeObject<List<User>>(json);
  • 高效集合运算:使用 LINQ 扩展方法(需 using System.Linq):
var union = list1.Union(list2).ToList();  // 并集 
var difference = list1.Except(list2);     // 差集 

2、Dictionary<TKey, TValue>(键值对字典)

(1)核心特性与优势
  • 哈希表实现
    • 基于哈希表存储键值对,提供接近 O(1) 的查找效率(性能取决于键的哈希算法质量)。
    • 示例:Dictionary<string, int> 比遍历 List 查找快数十倍。
  • 类型安全与强类型约束
    • 键 (TKey) 和值 (TValue) 均为泛型,编译时检查类型错误,避免 Hashtable 的装箱拆箱开销。
  • 动态扩容机制
    • 初始容量默认为 0,首次添加元素时扩容至 4,后续按指数级增长(4→8→16)。
    • 通过 Capacity 属性预设容量减少扩容开销。
(2)基础操作与语法
  • 初始化与增删改查
// 初始化 
var scores = new Dictionary<string, int>(); 
 
// 添加元素(键必须唯一)
scores.Add("Alice", 90);  
scores["Bob"] = 85;        // 索引器方式 
 
// 删除元素 
scores.Remove("Alice");
 
// 修改值 
scores["Bob"] = 95; 
 
// 安全取值(避免KeyNotFoundException)
if (scores.TryGetValue("Bob", out int score)) 
{
    Console.WriteLine(score); // 输出 95
}
  • 注意:直接通过 scores["Unknown"] 访问不存在的键会抛出异常。
  • 键值约束
    • 键 (TKey):不可为 null,且对象作为键时禁止修改影响哈希值的属性。
    • 值 (TValue):可为 null(若 TValue 是引用类型)。
(3)遍历字典的四种方式
方法语法示例适用场景
遍历 KeyValuePairforeach (var kvp in dict) { kvp.Key }需同时访问键和值(最常用)
单独遍历键集合foreach (var key in dict.Keys) { }仅需处理键(如批量删除)
单独遍历值集合foreach (var value in dict.Values) { }仅需统计或处理值
通过索引访问键var key = dict.ElementAt(0).Key;需按插入顺序访问(不推荐)
  • 提示:Dictionary 的元素顺序与添加顺序无关,需有序存储请改用 SortedDictionary
(4)高级应用场景
  • 数据缓存:利用 O(1) 查询特性存储频繁访问的数据(如数据库查询结果):
private static Dictionary<int, Product> _productCache = new();
public Product GetProduct(int id)
{
    if (!_productCache.TryGetValue(id, out var product))
    {
        product = FetchFromDatabase(id); 
        _productCache.Add(id, product);
    }
    return product;
}
  • 配置项管理:存储应用程序配置(如环境变量、用户设置):
var configs = new Dictionary<string, string> 
{
    ["Theme"] = "Dark",
    ["Timeout"] = "30"
};
  • 分组统计:快速聚合数据(如按类别计数):
var words = new List<string> { "apple", "banana", "apple" };
var countDict = new Dictionary<string, int>();
foreach (var word in words)
{
    countDict[word] = countDict.TryGetValue(word, out int count) ? count + 1 : 1;
}
(5)性能优化与陷阱规避
  • 预设初始容量:已知元素数量时,构造器指定容量避免多次扩容:var dict = new Dictionary<string, int>(capacity: 1000);
  • 避免修改键对象:若键对象被修改导致哈希值变化,该键将无法被检索到。
  • 线程安全问题:Dictionary 非线程安全,多线程环境需改用 ConcurrentDictionary 或手动加锁。
  • 优先使用 TryGetValue:比先 ContainsKey 再取值减少一次哈希计算。
(6)与相似类型的对比
集合类型特点适用场景
Dictionary哈希表实现,查询极快高频键值查找、缓存
SortedDictionary红黑树实现,按键排序需有序遍历键值对
Hashtable非泛型,需装箱拆箱遗留代码兼容(不推荐新项目)
ConcurrentDictionary线程安全版本多线程共享字典

3、Queue(先进先出队列)

(1)核心特性
  • 先进先出(FIFO)
    • 元素从队尾入队(Enqueue),从队头出队(Dequeue),确保最早加入的元素最先被处理。
    • 示例:
Queue<string> queue = new Queue<string>();
queue.Enqueue("First");  // 队尾添加 
queue.Enqueue("Second");
string firstItem = queue.Dequeue(); // 移除并返回"First"
  • 线程安全性
    • 非线程安全:默认实现不支持多线程并发操作,需手动加锁或使用 ConcurrentQueue<T>
    • 线程安全替代方案:
ConcurrentQueue<int> concurrentQueue = new ConcurrentQueue<int>();
concurrentQueue.Enqueue(100); // 线程安全入队 
  • 允许重复值与 null
    • 可存储重复元素,且接受 null 作为有效值(若 T 为引用类型)。
(2)基础操作与方法
方法/属性作用示例
Enqueue(T item)添加元素到队尾queue.Enqueue("NewItem");
Dequeue()移除并返回队头元素(队列为空时抛异常)string item = queue.Dequeue();
Peek()查看队头元素但不移除string head = queue.Peek();
Count获取队列中元素数量int num = queue.Count;
Contains(T item)检查元素是否存在bool exists = queue.Contains("A");
Clear()清空队列queue.Clear();
ToArray()将队列转为数组(顺序:队头→队尾)string[] arr = queue.ToArray();
(3)使用注意事项
  • 容量与性能优化
    • 默认初始容量为 4,扩容时容量翻倍(2×)。可通过构造函数指定初始容量减少扩容开销:
Queue<int> optimizedQueue = new Queue<int>(100); // 初始容量100 
- 调用 `TrimExcess()` 释放多余内存(当元素数 < 容量的90%时有效)。
  • 避免空队列操作:调用 Dequeue()Peek() 前需检查队列是否为空,否则抛出 InvalidOperationException
if (queue.Count > 0) {
    var item = queue.Dequeue();
}
(4)典型应用场景
  • 任务调度系统:按提交顺序处理任务(如订单处理、日志消费):
Queue<Action> taskQueue = new Queue<Action>();
taskQueue.Enqueue(() => Console.WriteLine("Task 1"));
taskQueue.Enqueue(() => Console.WriteLine("Task 2"));
while (taskQueue.Count > 0) {
    taskQueue.Dequeue().Invoke(); // 按序执行 
}
  • 广度优先搜索(BFS):遍历树或图结构时,用队列管理待访问节点:
Queue<TreeNode> nodes = new Queue<TreeNode>();
nodes.Enqueue(rootNode);
while (nodes.Count > 0) {
    var current = nodes.Dequeue();
    // 处理当前节点,子节点入队...
}
  • 消息缓冲机制:在多线程环境中,用 ConcurrentQueue<T> 实现生产者-消费者模型:
// 生产者线程 
concurrentQueue.Enqueue(newMessage);
// 消费者线程 
if (concurrentQueue.TryDequeue(out var msg)) {
    ProcessMessage(msg);
}
(5)与其他集合对比
集合类型顺序允许重复线程安全适用场景
Queue<T>FIFO❌(需手动同步)顺序任务处理、BFS
Stack<T>LIFO撤销操作、递归算法
ConcurrentQueue<T>FIFO高并发生产者-消费者模型
List<T>索引随机访问、动态数组需求

4、Stack(后进先出栈)

(1)核心特性
  • 后进先出(LIFO)结构:元素按插入顺序反向处理,最后添加的元素最先被移除。
(2)基础操作与方法
  • 初始化
Stack<int> stack = new Stack<int>();          // 空栈,默认容量 
Stack<string> stack2 = new Stack<string>(10); // 指定初始容量
  • Push(T item):元素压入栈顶。
stack.Push(1); 
stack.Push(2); // 栈顶元素为 2
  • Pop():移除并返回栈顶元素(栈空时抛 InvalidOperationException)。
int top = stack.Pop(); // 返回 2
  • Peek():查看栈顶元素不移除。
int currentTop = stack.Peek(); // 返回 1
  • TryPop(out T result) & TryPeek(out T result):安全操作,避免异常。
if (stack.TryPop(out int value)) 
{
    Console.WriteLine($"弹出元素:{value}");
}
(3)进阶用法与实践技巧
  • 遍历:遍历顺序为栈顶到栈底(逆插入顺序)
foreach (var item in stack) 
{
    Console.WriteLine(item); // 输出顺序:2 → 1 
}
  • 反转顺序:需转换为数组后反转。
var reversed = stack.ToArray().Reverse();
  • 资源清理与容量优化
    • 手动释放资源:结合 Clear() 清空栈元素。
    • 减少内存占用:调用 TrimExcess() 释放未用空间(当元素数小于容量的90%时生效)。
  • 线程安全场景
    • 多线程环境下使用 ConcurrentStack<T>
var concurrentStack = new ConcurrentStack<int>();
concurrentStack.Push(10);
concurrentStack.TryPop(out int result);
(4)性能与底层机制
  • 时间复杂度
    • Push()Pop() 操作在未触发扩容时为 O(1);扩容时(容量翻倍)为 O(n)。
    • Peek() 始终为 O(1)。
  • 内部实现
    • 基于动态数组存储,自动扩容策略为当前容量的 2 倍。
    • 初始容量过小可能导致频繁扩容,建议预估规模初始化。
  • 对比非泛型 Stack
    • 类型安全:避免装箱拆箱(值类型)和强制转换风险。
    • 性能提升:泛型版本减少运行时开销。
(5)典型应用场景
  • 撤销/重做功能:用两个栈分别存储操作记录:
Stack<ICommand> _undoStack = new Stack<ICommand>();
Stack<ICommand> _redoStack = new Stack<ICommand>();

// 执行操作
public void Execute(ICommand cmd) 
{
    cmd.Execute();
    _undoStack.Push(cmd);
    _redoStack.Clear(); // 清空重做栈
}

// 撤销操作 
public void Undo() 
{
    if (_undoStack.TryPop(out ICommand cmd)) 
    {
        cmd.Undo();
        _redoStack.Push(cmd);
    }
}
  • 深度优先搜索(DFS):递归算法的迭代实现
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.Push(rootNode);
while (stack.Count > 0) 
{
    var node = stack.Pop();
    Process(node);
    foreach (var child in node.Children.Reverse()) 
    {
        stack.Push(child); // 逆序压入子节点
    }
}
(6)注意事项
  • 空栈处理:调用 Pop()Peek() 前检查 Count 属性,或使用 TryPop/TryPeek 方法。
if (stack.Count > 0) 
{
    var item = stack.Peek();
}
  • 允许 null 值:引用类型栈可插入 null,需注意空值判断:
Stack<string> strStack = new Stack<string>();
strStack.Push(null); // 合法操作

5、HashSet(无序唯一集合)

(1)核心特性与底层机制
  • 唯一性与无序性
    • 元素唯一:自动过滤重复项(依赖 Equals()GetHashCode() 的正确实现)。
    • 无序存储:基于哈希表实现,不保证元素顺序(与 List<T> 的顺序存储对比鲜明)。
  • 高效操作
    • 时间复杂度:
      • Add()/Remove()/Contains():平均 O(1)(哈希冲突时退化至 O(n))。
      • 对比 List<T>Contains() 需遍历(O(n)),大数据量时性能差距显著。
    • 动态扩容:容量翻倍策略,预初始化可优化性能(如 new HashSet<T>(capacity))。
  • 允许 null
    • 引用类型可插入 null,需额外判空处理。
️(2)核心方法
  • 基础操作示例
var emails = new HashSet<string>();
emails.Add("user@example.com");   // 添加成功 → true
emails.Add("user@example.com");   // 添加失败 → false(重复忽略)
bool exists = emails.Contains("user@example.com");  // 检查存在 → true 
emails.Remove("user@example.com");
  • 集合运算(高频应用)
方法作用示例
UnionWith()并集(合并集合)set1.UnionWith(set2);
IntersectWith()交集(共同元素)set1.IntersectWith(set2);
ExceptWith()差集(移除共有元素)set1.ExceptWith(set2);
SymmetricExceptWith()对称差集(独有元素)set1.SymmetricExceptWith(set2);
(3)典型场景
  • 数据去重:替代 List<T> 的遍历去重,效率更高。
int[] numbers = { 1, 2, 2, 3, 4 };
var uniqueSet = new HashSet<int>(numbers); // 结果:{1, 2, 3, 4}
  • 快速检索:如用户邮箱唯一性校验(10万级数据比 List<T> 快百倍):
if (emailSet.Contains(inputEmail)) // O(1) 完成校验
  • 内存数据库缓存:高频访问数据用 HashSet 缓存,减少数据库查询(需定时刷新防回收)。
(4)与 List<T> 的关键对比
维度HashSet<T>List<T>
唯一性✅ 强制唯一❌ 允许重复
顺序性❌ 无序✅ 保留插入顺序
查找性能O(1)(哈希直接定位)🐢 O(n)(需遍历)
内存占用更低(无重复预留空间)更高(可能预留多余容量)
适用场景去重、集合运算、高频检索顺序访问、索引操作、允许重复
️(5)注意事项与进阶技巧
  • 线程安全:非线程安全!多线程环境需用 ConcurrentDictionary<T, byte> 或加锁。
  • 哈希冲突优化
    • 重写 GetHashCode() 确保散列均匀(避免大量冲突导致性能下降)。
  • 内存回收
    • 调用 TrimExcess() 释放未用空间(元素数 < 容量的 90% 时生效)。
  • 与 LINQ 结合
    • 支持 LINQ 操作,但部分方法(如 Distinct())在 HashSet 中冗余:
var evenNumbers = numberSet.Where(n => n % 2 == 0); // 直接过滤
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李宥小哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值