一个被BCL遗忘的高性能集合:C# CircularBuffer<T>深度解析
一个被BCL遗忘的高性能集合C# CircularBuffer深度解析在 .NET 的 BCLBase Class Library中我们习惯了ListT、DictionaryTKey, TValue、QueueT这些常用集合。但有一个高性能数据结构——环形缓冲区Circular Buffer它既不在System.Collections.Generic命名空间下也没有被官方文档重点提及却在高性能日志、网络传输、实时数据流处理中扮演着关键角色。本文将带你从零开始深度解析如何在 C# 中实现并应用一个高效的CircularBufferT。—## 1. 为什么需要环形缓冲区想象一个场景你正在处理网络数据包数据到达速率不稳定有时每秒上千条有时几秒才来一条。如果使用ListT存储所有数据内存会无限增长如果使用QueueT但只保留最近 N 条则需要频繁出队和入队且QueueT内部数组扩容时会有性能开销和内存碎片。环形缓冲区的核心思想是复用固定大小的内存块通过头尾指针实现“覆盖旧数据”的逻辑。它适用于- 实时数据流如传感器数据、股票行情- 日志滚动写入只保留最近 1000 条日志- 生产者/消费者模式下的有界队列—## 2. 基础概念从数组到环形我们从一个简单数组开始csharp// 基础数组长度固定但无法循环利用空间int[] buffer new int[5];环形缓冲区的核心是两个索引-_head指向下一个要写入的位置-_tail指向下一个要读取的位置当_head到达数组末尾时它回绕到开头通过取模运算从而形成“环”。下面是一个最小实现csharppublic class SimpleCircularBufferT{ private readonly T[] _buffer; private int _head; // 写指针 private int _tail; // 读指针 private int _count; // 当前元素数量 public SimpleCircularBuffer(int capacity) { _buffer new T[capacity]; _head 0; _tail 0; _count 0; } public void Enqueue(T item) { if (_count _buffer.Length) throw new InvalidOperationException(Buffer is full); _buffer[_head] item; _head (_head 1) % _buffer.Length; _count; } public T Dequeue() { if (_count 0) throw new InvalidOperationException(Buffer is empty); T item _buffer[_tail]; _tail (_tail 1) % _buffer.Length; _count--; return item; }}这个版本已能工作但有几个问题1. 满了之后无法覆盖旧数据抛异常2. 没有线程安全3. 性能上每次取模有硬件开销但现代 CPU 可优化—## 3. 进阶支持覆盖的循环缓冲区在日志场景中我们通常希望缓冲区满时自动覆盖最旧的数据。修改Enqueue逻辑csharppublic class OverwriteCircularBufferT{ private readonly T[] _buffer; private int _head; private int _tail; private int _count; public OverwriteCircularBuffer(int capacity) { _buffer new T[capacity]; } public void Enqueue(T item) { _buffer[_head] item; _head (_head 1) % _buffer.Length; if (_count _buffer.Length) _tail (_tail 1) % _buffer.Length; // 覆盖时移动读指针 else _count; } public T Dequeue() { if (_count 0) throw new InvalidOperationException(Empty); T item _buffer[_tail]; _tail (_tail 1) % _buffer.Length; _count--; return item; } public int Count _count; public int Capacity _buffer.Length;}这里的关键是当缓冲区已满时写入新数据会导致_tail也向前移动从而丢弃最旧的数据。这种设计非常适合“只保留最近 N 条”的场景。—## 4. 高级优化避免取模运算虽然取模运算在 .NET 中经过优化但在高频写入如每秒百万次时仍可能成为瓶颈。一种经典优化是让容量为 2 的幂然后用位运算替代取模csharppublic class FastCircularBufferT{ private readonly T[] _buffer; private int _head; private int _tail; private int _count; private readonly int _mask; // 容量-1 public FastCircularBuffer(int capacity) { // 将容量向上取整为2的幂 capacity (int)Math.Pow(2, Math.Ceiling(Math.Log(capacity, 2))); _buffer new T[capacity]; _mask capacity - 1; } public void Enqueue(T item) { _buffer[_head] item; _head (_head 1) _mask; // 位运算代替取模 if (_count _buffer.Length) _tail (_tail 1) _mask; else _count; } public T Dequeue() { if (_count 0) throw new InvalidOperationException(Empty); T item _buffer[_tail]; _tail (_tail 1) _mask; _count--; return item; }}注意 _mask等价于% _buffer.Length但速度更快。此优化在 .NET 7 中尤其明显因为 JIT 会自动识别某些模式。—## 5. 完整示例实时数据缓存下面是一个完整的可运行示例演示如何使用环形缓冲区缓存股票价格并显示最近 10 条数据csharpusing System;using System.Threading;class Program{ static void Main() { // 创建一个容量为5的环形缓冲区 var buffer new OverwriteCircularBufferdouble(5); // 模拟产生数据 Random rand new Random(); for (int i 0; i 10; i) { double price 100 rand.NextDouble() * 10; buffer.Enqueue(price); Console.WriteLine($写入: {price:F2} | 当前数量: {buffer.Count}); Thread.Sleep(100); } Console.WriteLine(\n--- 读取所有数据应只有最近5条 ---); while (buffer.Count 0) { Console.WriteLine($读取: {buffer.Dequeue():F2}); } }}// 复用上面的 OverwriteCircularBufferT 类public class OverwriteCircularBufferT{ private readonly T[] _buffer; private int _head; private int _tail; private int _count; public OverwriteCircularBuffer(int capacity) { _buffer new T[capacity]; } public void Enqueue(T item) { _buffer[_head] item; _head (_head 1) % _buffer.Length; if (_count _buffer.Length) _tail (_tail 1) % _buffer.Length; else _count; } public T Dequeue() { if (_count 0) throw new InvalidOperationException(Empty); T item _buffer[_tail]; _tail (_tail 1) % _buffer.Length; _count--; return item; } public int Count _count; public int Capacity _buffer.Length;}运行结果示例写入: 105.32 | 当前数量: 1写入: 103.87 | 当前数量: 2...写入: 108.11 | 当前数量: 5写入: 107.55 | 当前数量: 5 // 开始覆盖...--- 读取所有数据应只有最近5条 ---读取: 106.44读取: 104.29读取: 108.11读取: 107.55读取: 102.93—## 6. 线程安全与性能考量BCL 中的System.Collections.Concurrent提供了ConcurrentQueueT但它是无界或有界的有界版本需要 .NET 8 的BoundedChannel。环形缓冲区在单生产者单消费者场景下可以做到无锁csharppublic class LockFreeCircularBufferT{ private readonly T[] _buffer; private int _head; private int _tail; private int _count; private readonly int _mask; public LockFreeCircularBuffer(int capacity) { capacity (int)Math.Pow(2, Math.Ceiling(Math.Log(capacity, 2))); _buffer new T[capacity]; _mask capacity - 1; } // 要求单生产者调用 public void Enqueue(T item) { // 假设有空间否则覆盖 _buffer[_head] item; _head (_head 1) _mask; Interlocked.Increment(ref _count); // 原子更新 } // 要求单消费者调用 public bool TryDequeue(out T result) { if (Volatile.Read(ref _count) 0) { result default; return false; } result _buffer[_tail]; _tail (_tail 1) _mask; Interlocked.Decrement(ref _count); return true; }}注意Interlocked操作有一定开销但比锁lock快得多。在极高性能需求下可以使用System.Threading.SpinLock或内存屏障。—## 7. 总结环形缓冲区是 BCL 中“隐藏的宝石”——虽然官方没有提供通用实现但它在功能上与QueueT互补在性能上优于ListT的频繁插入删除。本文从基础数组环开始逐步优化到支持覆盖、位运算加速、无锁并发展示了其在不同场景下的应用价值。核心要点回顾1.固定内存避免频繁扩容和 GC 压力2.覆盖机制适合“只保留最近 N 条”的数据流3.性能优化容量设为 2 的幂用代替%4.并发支持单生产者单消费者场景可无锁如果你在开发实时系统、日志库或游戏服务器中的消息队列不妨抛弃ListT自己实现一个CircularBufferT——它可能成为你工具箱中最锋利的刀。BCL 没有提供不代表它不重要而是给了你自由定制的机会。

相关新闻

7-Zip-zstd:免费增强版压缩工具,让文件压缩效率提升300%

7-Zip-zstd:免费增强版压缩工具,让文件压缩效率提升300%

7-Zip-zstd:免费增强版压缩工具,让文件压缩效率提升300% 【免费下载链接】7-Zip-zstd 7-Zip with support for Brotli, Fast-LZMA2, Lizard, LZ4, LZ5 and Zstandard 项目地址: https://gitcode.com/gh_mirrors/7z/7-Zip-zstd 你是否还在使用传统…

2026/9/25 4:48:52 阅读更多 →
WebPShop:Photoshop终极WebP格式插件解决方案

WebPShop:Photoshop终极WebP格式插件解决方案

WebPShop:Photoshop终极WebP格式插件解决方案 【免费下载链接】WebPShop Photoshop plug-in for opening and saving WebP images 项目地址: https://gitcode.com/gh_mirrors/we/WebPShop 还在为Photoshop的WebP支持功能不完整而烦恼吗?WebPShop插…

2026/9/25 7:23:53 阅读更多 →
Ohook:终极免费Office激活方案,3分钟永久解锁Microsoft 365完整功能

Ohook:终极免费Office激活方案,3分钟永久解锁Microsoft 365完整功能

Ohook:终极免费Office激活方案,3分钟永久解锁Microsoft 365完整功能 【免费下载链接】ohook An universal Office "activation" hook with main focus of enabling full functionality of subscription editions 项目地址: https://gitcode.…

2026/9/25 6:51:26 阅读更多 →

最新新闻

UDS诊断中的P4Server:CAN与DoIP差异及NRC 0x78处理技巧

UDS诊断中的P4Server:CAN与DoIP差异及NRC 0x78处理技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 7:32:53 阅读更多 →
AI Agent工具链实战:CLI、MCP与OpenRouter集成指南

AI Agent工具链实战:CLI、MCP与OpenRouter集成指南

1. 从"treg"这个模糊词说起:它到底指什么第一次看到"treg"这个词,很多人会一头雾水。它不像"codex cli"或者"openrouter"那样有明确的指向,更像是一个被截断的缩写或者内部代号。结合热搜词里高频出…

2026/9/25 7:32:53 阅读更多 →
Windows内核非分页池泄漏诊断:PoolMon与RAMMap实战指南

Windows内核非分页池泄漏诊断:PoolMon与RAMMap实战指南

1. 这不是“内存不足”,是内核在悄悄吃掉你的RAM 你有没有遇到过这种情况:刚重启的 Windows 11,任务管理器显示“已使用内存”只有 3GB,但系统却卡得像在用软盘加载高清视频?打开 Chrome 多几个标签页,内存…

2026/9/25 7:32:53 阅读更多 →
Fast-LIO2在ROS2上的部署实践与避坑手册

Fast-LIO2在ROS2上的部署实践与避坑手册

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 7:32:53 阅读更多 →
GEF 逆向实战:用 pattern 命令基于 De Bruijn 序列定位溢出偏移量

GEF 逆向实战:用 pattern 命令基于 De Bruijn 序列定位溢出偏移量

网络安全开发工具 【免费下载链接】gef GEF (GDB Enhanced Features) - a modern experience for GDB with advanced debugging capabilities for exploit devs & reverse engineers on Linux 项目地址: https://gitcode.com/gh_mirrors/gef/gef 点击查看 免费下…

2026/9/25 7:32:52 阅读更多 →
小米平板4 Plus刷Droidian:从解锁分区到蓝牙修复的完整指南

小米平板4 Plus刷Droidian:从解锁分区到蓝牙修复的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 7:31:52 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →