Stack<T> 学习笔记
一、基础概念与用法
1. 什么是 Stack?
Stack<T> 是一种遵循 “后进先出”(LIFO: Last In, First Out) 原则的泛型集合。
-
类比理解:
就像往一个筒里放书——只能从顶部放入,也只能从顶部取出。最后放进去的书,最先被拿出来。 -
命名空间:
using System.Collections.Generic;
2. 创建 Stack
// 空栈
Stack<string> stack = new Stack<string>();
// 初始化时添加元素
Stack<int> numbers = new Stack<int>(new int[] { 10, 20, 30 });
// 栈中从底到顶:10 → 20 → 30(30 在最上面)
3. 常用方法与属性
| 方法 / 属性 | 作用说明 | 时间复杂度 |
|---|---|---|
Push(item) |
将元素压入栈顶 | O(1) |
Pop() |
移除并返回栈顶元素(会修改栈) | O(1) |
Peek() |
查看栈顶元素,但不移除 | O(1) |
Count |
获取栈中元素数量 | O(1) |
Clear() |
清空整个栈 | O(n) |
Contains(item) |
判断是否包含某元素 | O(n) |
ToArray() |
将栈转为数组(顺序:从顶到底) | O(n) |
⚠️ 重要提醒:
调用Pop()或Peek()时,若栈为空,会抛出InvalidOperationException。
安全做法:先检查Count > 0。
4. 基础示例
Stack<string> actions = new Stack<string>();
actions.Push("保存文件");
actions.Push("编辑文本");
actions.Push("打开文档");
Console.WriteLine($"待处理操作数:{actions.Count}"); // 输出:3
Console.WriteLine($"当前操作:{actions.Peek()}"); // 输出:打开文档
string current = actions.Pop();
Console.WriteLine($"执行:{current}"); // 输出:执行:打开文档
// 遍历(从顶到底)
foreach (string action in actions)
{
Console.WriteLine(action);
}
// 输出:
// 编辑文本
// 保存文件
💡 注意:
foreach遍历顺序是 从栈顶到栈底,不是插入顺序。
二、进阶知识点
1. 与其他集合的对比
| 集合类型 | 访问方式 | 插入/删除位置 | 顺序性 | 典型用途 |
|---|---|---|---|---|
List<T> |
索引随机访问 | 任意位置 | 是 | 通用列表、频繁读写 |
Queue<T> |
先进先出 (FIFO) | 队尾入,队头出 | 是 | 任务队列、消息缓冲 |
Stack<T> |
后进先出 (LIFO) | 仅栈顶 | 是 | 撤销、回溯、匹配 |
HashSet<T> |
无索引 | 任意(自动去重) | 否 | 快速查找、去重 |
✅ 关键区别:
Stack<T>只允许在栈顶操作,设计目标明确,不适合随机访问。
2. 性能特点
-
底层实现:基于动态数组(
T[]),自动扩容。 -
高效操作:
Push/Pop/Peek:O(1) ,非常快。
-
低效操作:
Contains/Clear/ToArray:O(n) ,需遍历全部元素。
-
内存开销:略高于数组,但远低于链表类结构。
📌 建议:
若需频繁“压入/弹出”,用Stack<T>;
若需频繁“查找/中间插入”,改用List<T>。
3. 线程安全问题
-
Stack<T>不是线程安全的。- 多线程同时操作可能导致异常或数据错乱。
解决方案:
-
手动加锁(适用于简单场景)
private static readonly object _lock = new(); lock (_lock) { myStack.Push(item); } -
使用线程安全版本:****
ConcurrentStack<T>using System.Collections.Concurrent; ConcurrentStack<int> safeStack = new(); safeStack.Push(42); bool success = safeStack.TryPop(out int value); // 安全弹出
✅ 初学者建议:
单线程 →Stack<T>;
多线程 → 优先选择ConcurrentStack<T>。
三、实际工作中的使用场景
场景 1:撤销(Undo)功能
-
原理:每次操作压入栈,撤销时弹出并反向执行。
-
示例:
Stack<Action> undoActions = new(); // 用户输入 undoActions.Push(() => textBox.Text = previousText); // 撤销 if (undoActions.Count > 0) { undoActions.Pop().Invoke(); // 执行撤销动作 }
场景 2:括号匹配检查
-
用途:校验
{[()]}、HTML 标签是否正确闭合。 -
逻辑:
- 遇到左括号 →
Push - 遇到右括号 →
Pop并验证是否匹配 - 最终栈应为空
- 遇到左括号 →
bool IsValid(string s)
{
Stack<char> stack = new();
var pairs = new Dictionary<char, char> { { ')', '(' }, { ']', '[' }, { '}', '{' } };
foreach (char c in s)
{
if ("([{".Contains(c))
stack.Push(c);
else if (stack.Count == 0 || stack.Pop() != pairs[c])
return false;
}
return stack.Count == 0;
}
场景 3:浏览器“后退”功能
-
实现:
- 访问新页面 →
Push到历史栈 - 点击“后退” →
Pop当前页,显示新栈顶
- 访问新页面 →
场景 4:深度优先搜索(DFS)
-
在树或图遍历中,非递归 DFS 通常用 Stack 实现。
-
示例(遍历文件夹):
Stack<string> folders = new(); folders.Push(rootPath); while (folders.Count > 0) { string current = folders.Pop(); foreach (string sub in Directory.GetDirectories(current)) folders.Push(sub); // 处理 current }
场景 5:表达式求值与编译器
- 中缀表达式 → 后缀表达式(逆波兰)转换
- 运算符优先级处理
- 函数调用栈模拟(底层原理)
总结速查表
| 项目 | 说明 |
|---|---|
| 核心原则 | 后进先出(LIFO) |
| 关键操作 | Push(入)、Pop(出)、Peek(看) |
| 性能优势 | O(1) 的入栈/出栈 |
| 常见陷阱 | 空栈调用 Pop/Peek 会崩溃 |
| 线程安全 | 否 → 用 ConcurrentStack<T> |
| 典型场景 | 撤销、匹配、回退、DFS、表达式解析 |
🌟 一句话口诀:
“栈顶操作,后进先出;高效简洁,慎防空栈。”

无匣也无鞘,暗室夜常明。三尺木马牛,可折天下兵。欲知天将雨,铮铮发龙鸣。提剑走人间,百鬼夜遁行。飞过广陵江,八百蛟龙惊。世人不知何所求,那袭青衫放声笑:天不生我李淳罡,剑道万古如长夜!
浙公网安备 33010602011771号