C#基础14-非泛型集合
1、ArrayList
(1)核心特性
- 动态数组
- 无需预先定义固定长度,容量随元素增减自动调整(默认初始容量为 4,扩容时通常加倍)。
- 支持任意类型元素(存储为
object
类型),可混合存放字符串、整数等。
- 底层实现
- 继承
System.Collections
命名空间,实现 IList
、ICollection
等接口。 - 非泛型集合,依赖装箱(值类型→
object
)和拆箱(object
→值类型)操作。
(2)基本操作与示例
using System.Collections;ArrayList al = new ArrayList();
al.Add("Apple");
al.Insert(0, 123);
al.AddRange(new[] { "Banana", 4.5 });
al.Remove("Apple");
al.RemoveAt(0);
al.Clear();
int index = al.IndexOf("Banana");
al.Sort();
al.Reverse();
(3)优缺点分析
优点 | 缺点 |
---|
✅ 动态扩容,无需预设大小 | ❌ 类型不安全:编译期无类型检查,运行时易出错 |
✅ 灵活插入/删除(Insert /Remove ) | ❌ 性能损耗:值类型操作需装箱拆箱,效率低 |
✅ 支持多种数据混合存储 | ❌ 非线程安全:多线程需手动同步 |
(4)与 List<T>
的对比
特性 | ArrayList | List<T> (泛型) |
---|
类型安全 | ❌ 存储 object 类型 | ✅ 编译时类型检查 |
性能 | ❌ 装箱拆箱开销大 | ✅ 无需装箱拆箱,效率高 |
内存占用 | ❌ 扩容频繁可能浪费内存 | ✅ 容量管理更高效 |
推荐场景 | 遗留代码维护 | 新项目首选,尤其值类型操作 |
(5)注意事项
- 线程安全:需通过
ArrayList.Synchronized()
创建线程安全版本。 - 排序限制:混合类型时调用
Sort()
会抛出 InvalidOperationException
,需确保元素可比。
2、Hashtable
(1)核心特性
- 键值对存储
- 以
key-value
形式存储数据,键和值均为 object
类型,支持任意数据类型(如字符串、整数、自定义对象)。 - 键唯一性:键不能重复,尝试插入重复键会覆盖原有值。
- 哈希算法驱动
- 通过键的哈希码(
GetHashCode()
生成)定位数据存储位置,实现 O(1) 平均时间复杂度的快速查找。 - 自动处理哈希冲突:采用链地址法(冲突键值对存储在同一个桶的链表中)。
- 动态扩容
- 初始默认容量为 0(首次添加元素时扩容至合理值),当元素数量超过当前容量与负载因子(默认 0.75)乘积时,自动扩容并重新分配存储。
- 非泛型与非线程安全
- 位于
System.Collections
命名空间,存储时需装箱拆箱(值类型→object
),性能低于泛型集合。 - 默认非线程安全,多线程读写需通过
Hashtable.Synchronized()
包装或手动加锁。
(2)基础用法
using System.Collections;Hashtable ht = new Hashtable();
ht.Add("id", 1001);
ht["name"] = "Alice";
ht.Add(3.14, "Pi");
ht.Remove("id");
ht.Clear();
if (ht.ContainsKey("name")) Console.WriteLine(ht["name"]);
if (ht.ContainsValue(1001)) Console.WriteLine("Value exists!");
string name = ht["name"] as string;
if (name != null) { }
if (ht["id"] is int id) Console.WriteLine($"ID: {id}");
foreach (object key in ht.Keys) Console.WriteLine($"Key: {key}");
foreach (object value in ht.Values) Console.WriteLine($"Value: {value}");
foreach (DictionaryEntry de in ht) Console.WriteLine($"{de.Key} : {de.Value}");
ArrayList keys = new ArrayList(ht.Keys);
keys.Sort();
foreach (string key in keys) Console.WriteLine($"{key}: {ht[key]}");
️(3)与 Dictionary<TKey, TValue>
对比
特性 | Hashtable | Dictionary<TKey, TValue> |
---|
类型安全 | ❌ 需手动类型转换 | ✅ 编译时类型检查 |
性能 | ❌ 值类型操作需装箱拆箱,效率低 | ✅ 无装箱拆箱,速度快20%-50% |
线程安全 | ❌ 需额外同步机制 | ❌ 默认非线程安全(可用并发集合替代) |
顺序保证 | ❌ 无序存储 | ✔️ .NET Core 3.0+ 默认按插入顺序遍历 |
推荐场景 | 遗留代码维护、需混合类型存储 | 新项目首选,高性能场景 |
️(4)注意事项
- 键不可为
null:
尝试添加 null
键会抛出 ArgumentNullException
。 - 哈希码依赖:自定义对象作为键时,需重写
GetHashCode()
和 Equals()
方法,确保哈希一致性。 - 混合类型排序风险:若键类型不一致(如字符串与整数混合),调用
Sort()
可能抛出 InvalidOperationException
。
3、Queue
(1)核心特性
- 先进先出(FIFO)机制
- 元素按插入顺序处理,队头元素(最早入队)优先被移除。
- 典型场景:任务调度、消息传递、缓冲区管理(如日志处理)。
- 动态扩容
- 初始容量为 0(首次添加时自动扩容),当元素数量超过当前容量时自动扩容(通常加倍)。
- 支持手动调整容量:
TrimToSize()
优化内存。
- 非泛型与泛型实现
System.Collections.Queue
:存储 object
类型,需装箱拆箱,类型不安全。System.Collections.Generic.Queue<T>
:泛型版本,类型安全且无需装箱拆箱。
- 基础操作高效性
- 入队(
Enqueue
)、出队(Dequeue
)、查看队头(Peek
)操作时间复杂度为 O(1)。
(2)基础用法
using System.Collections.Generic;Queue<string> queue = new Queue<string>();
queue.Enqueue("Task1");
queue.Enqueue("Task2");
string task = queue.Dequeue();
string nextTask = queue.Peek();
queue.Clear();
foreach (var item in queue) {Console.WriteLine(item);
}
if (queue.Contains("Task2")) {Console.WriteLine("任务存在");
}
lock (queue) {queue.Enqueue("ConcurrentTask");
}
(3)优缺点分析
优点 | 缺点 |
---|
✅ 动态扩容,无需预设大小 | ❌ 非泛型版本需装箱拆箱,性能损耗 |
✅ FIFO 机制保证处理顺序 | ❌ 默认非线程安全,需额外同步 |
✅ 基础操作(入队/出队)高效 | ❌ 无法直接访问队列中间元素 |
(4)与现代替代方案对比
特性 | Queue<T> | ConcurrentQueue<T> |
---|
线程安全 | ❌ 需手动加锁 | ✅ 无锁实现,内置线程安全 |
性能 | ✅ 单线程下更快 | ✅ 高并发下性能更优(避免锁竞争) |
API 设计 | Enqueue /Dequeue | TryEnqueue /TryDequeue (避免异常) |
适用场景 | 单线程或低并发任务 | 高并发场景(如并行计算、消息队列) |
操作 | Queue<T> 耗时 | ConcurrentQueue<T> 耗时 |
---|
单线程入队 | 15 ms | 20 ms |
多线程入队 | 崩溃(线程竞争) | 35 ms |
Channel
(.NET Core 3.0+) - 定位:异步消息队列,替代生产者-消费者模式中的
ConcurrentQueue
。 - 优势:
- 支持异步写入(
WriteAsync
)和读取(ReadAsync
)。 - 内置背压控制(限制队列长度)。
(5)使用建议
- 首选泛型版本:新项目一律使用
Queue<T>
,避免类型转换和装箱开销。 - 并发场景选择
- 多线程任务调度 →
ConcurrentQueue<T>
。 - 异步数据流处理 →
Channel
。
- 避免混合类型:非泛型
Queue
仅用于遗留代码维护或未知类型数据(如动态解析 JSON)。
4、Stack
(1)核心特性
- 后进先出(LIFO)机制
- 最后添加的元素最先被移除,适用于撤销操作、表达式求值等场景。
- 动态扩容
- 初始容量为 0(首次添加时自动扩容),元素超限时容量加倍。
- 两种实现方式
System.Collections.Stack
:非泛型,存储 object
类型,需装箱拆箱。System.Collections.Generic.Stack<T>
:泛型版本,类型安全且无性能损耗。
- 基础操作高效
- 入栈(
Push
)、出栈(Pop
)、查看栈顶(Peek
)的时间复杂度为 O(1) 。
(2)基础操作与示例
using System.Collections;Stack st = new Stack();
st.Push(100);
st.Push("Text");
foreach (object obj in st) {if (obj is string text) Console.WriteLine(text);
}
(3)关键注意事项
lock (stack) {stack.Push("concurrent_item");
}
- 空栈操作风险:
Pop()
或 Peek()
空栈会抛异常,优先使用 TryPop()
/TryPeek()
(泛型)或检查 Count > 0
if (stack.TryPop(out string result)) Console.WriteLine($"出栈元素: {result}");
- 排序与遍历限制
- 无法直接排序,需转数组后操作(
stack.ToArray().Sort()
)。 - 遍历顺序为 LIFO(从栈顶到栈底)。
(4)与现代替代方案对比
特性 | 非泛型 Stack | 泛型 Stack<T> | ConcurrentStack<T> |
---|
类型安全 | ❌ 需手动类型转换 | ✅ 编译时类型检查 | ✅ 类型安全 |
性能 | ❌ 值类型需装箱拆箱 | ✅ 无装箱拆箱,高效 | ✅ 无锁设计,高并发优化 |
线程安全 | ❌ 需手动同步 | ❌ 需手动同步 | ✅ 内置线程安全 |
推荐场景 | 遗留代码维护、动态类型数据 | 新项目首选(类型明确场景) | 高并发任务(如并行计算) |
(5)典型应用场景
Stack<string> history = new Stack<string>();
history.Push("State1");
history.Push("State2");
string lastState = history.Pop();
bool IsValid(string s) {Stack<char> stack = new Stack<char>();foreach (char c in s) {if (c == '(') stack.Push(')');else if (stack.Count == 0 || stack.Pop() != c) return false;}return stack.Count == 0;
}
- 递归函数转迭代:用栈模拟递归调用栈,避免堆栈溢出。