×

C# BinaryWriter/Reader高效数据存储实战

独孤求败 独孤求败 发表于2026-10-10 09:27:41 浏览20 评论0

抢沙发发表评论

聚焦 C# 与工业数字化深度融合,本专栏由资深工业软件开发专家主理,系统分享 .NET 平台在智能制造、工业自动化及 MES/SCADA 系统开发中的实战经验。内容涵盖 OPC UA、Modbus、MQTT 等工业通信协议集成、实时数据采集与处理、数字孪生建模及边缘计算部署等核心技术,兼顾架构设计与代码落地。无论你是工控工程师还是 .NET 程序员,这里都有你需要的技术干货,助你在工业数字化浪潮中构建真正有价值的应用系统。

 

 

你有没有遇到过这种情况:高速冲压机一秒钟采集几百条压力数据,存成JSON文件没多久硬盘就报警空间不足,程序读取的时候也感觉像老牛拉车一样慢。

今天这篇,就是教你用二进制存储高频采集数据,同样的数据量,文件能小一大半,读写速度还能明显提升。

📌 上节回顾

上一节我们学了 XML序列化与XDocument,掌握了处理老系统XML格式数据的方法。今天在这个基础上,我们进一步学习二进制序列化——用最紧凑的方式存储工业数据。

💡 核心知识讲解

先说清楚一件事:BinaryFormatter已经被淘汰了

如果你在网上搜"C#二进制序列化",可能会看到不少教程用 BinaryFormatter,但这个类在最新的.NET版本里已经被官方标记为不安全,直接被移除了,千万别再用它。

现在正确的做法是用 BinaryWriter 和 BinaryReader,手动控制怎么把数据写成二进制,怎么从二进制读回来,虽然要多写几行代码,但更安全、更可控。

为什么工业场景特别需要二进制存储

JSON、XML这些文本格式,说白了就是"人能看懂的文字",一个数字比如 95.5,存成文本要占好几个字节,但如果用二进制直接存这个数字本身,占用空间小得多。

这就好比JSON是把数据翻译成中文写在纸上,二进制是直接把原始数据打包压缩,虽然人看不懂,但存储和传输效率高得多。

存储方式
文件大小
读写速度
人能否直接看懂
JSON/XML文本
较大
较慢
能
二进制
小很多
快很多
不能,需要程序解析

「一句话记住:给人看的配置文件用JSON/XML,给程序自己高频读写的海量数据用二进制。」

BinaryWriter/BinaryReader:手动打包的正确工具

BinaryWriter 提供了一堆 Write 方法,可以直接把数字、字符串按照固定格式写入文件,BinaryReader 则按照写入时的顺序,一个一个把数据读回来。

writer.Write(deviceTemp);      // 写入一个double类型数值
writer.Write(deviceId);        // 写入一个字符串

「写的顺序和读的顺序必须完全一致」,就像你打包快递时东西的摆放顺序,拆包的时候也得按同样的顺序取出来,顺序错了数据就全乱了。

什么场景适合用二进制存储

设备的高频采集数据(比如每秒几十上百条的传感器读数)、需要长期归档但不需要人工查看的历史记录,这些场景特别适合用二进制存储,省空间又省时间。

配置文件、需要人工检查修改的参数,还是老老实实用JSON或XML,方便肉眼查看和排查问题。

「重要提醒:二进制文件打开后是一堆乱码,出问题了没法直接肉眼排查,一定要配合日志记录关键操作,方便追溯问题。」

💻 VS2026 操作步骤

Step 1 新建控制台项目:文件 > 新建 > 项目 > 选择"控制台应用",命名 BinaryStorageDemo。

Step 2 BinaryWriter 和 BinaryReader 都在 System.IO 命名空间下,不需要额外安装NuGet包。

Step 3 写Write/Read代码时,对Copilot说明数据结构和字段顺序,AI会自动生成配套的写入和读取代码,并确保顺序一致。

Step 4 写完代码后,选中整段右键 > Copilot > 检查代码质量,AI会提醒你写入和读取顺序是否匹配,这是二进制读写最容易出错的地方。

Vibe Coding Prompt 示例:

帮我写一段C#代码,用BinaryWriter把设备编号(字符串)、采集温度(double)、采集时间戳(long)按顺序写入一个二进制文件,再用BinaryReader按同样顺序读回来验证

📋 完整代码示例

这段代码模拟高速冲压机的压力传感器采集数据,用BinaryWriter批量写入二进制文件,再用BinaryReader读回来验证数据完整性。

using System;
using System.IO;
using System.Collections.Generic;

namespaceBinaryStorageDemo
{
    // 压力采集记录结构
    classPressureRecord
    {
        publicstring DeviceId { get; set; } = "";
        publicdouble PressureValue { get; set; }
        publiclong TimestampTicks { get; set; }
    }

    classProgram
    {
        static void Main()
        {
            string binaryFilePath = @"D:\StampingData\pressure_records.bin";
            string folderPath = Path.GetDirectoryName(binaryFilePath)!;

            if (!Directory.Exists(folderPath))
            {
                Directory.CreateDirectory(folderPath);
            }

            // 生成模拟的高频压力采集数据
            List<PressureRecord> records = GenerateSampleRecords(1000);

            // 用BinaryWriter写入二进制文件
            WriteRecordsToBinary(binaryFilePath, records);

            // 用BinaryReader读回二进制文件并验证
            List<PressureRecord> loadedRecords = ReadRecordsFromBinary(binaryFilePath);

            Console.WriteLine($"共读取到 {loadedRecords.Count} 条记录");
            Console.WriteLine("前3条记录示例:");
            for (int i = 0; i < 3 && i < loadedRecords.Count; i++)
            {
                var r = loadedRecords[i];
                DateTime recordTime = new DateTime(r.TimestampTicks);
                Console.WriteLine($"设备:{r.DeviceId},压力:{r.PressureValue:F2},时间:{recordTime:HH:mm:ss.fff}");
            }

            // 对比文件大小
            FileInfo fileInfo = new FileInfo(binaryFilePath);
            Console.WriteLine($"二进制文件大小:{fileInfo.Length} 字节");
        }

        // 生成模拟压力采集数据
        static List<PressureRecord> GenerateSampleRecords(int count)
        {
            List<PressureRecord> records = new List<PressureRecord>();
            Random rnd = new Random();

            for (int i = 0; i < count; i++)
            {
                records.Add(new PressureRecord
                {
                    DeviceId = "冲压机01",
                    PressureValue = 100 + rnd.NextDouble() * 50,
                    TimestampTicks = DateTime.Now.AddMilliseconds(i * 10).Ticks
                });
            }

            return records;
        }

        // 用BinaryWriter把记录列表写入二进制文件
        static void WriteRecordsToBinary(string filePath, List<PressureRecord> records)
        {
            using (FileStream fs = new FileStream(filePath, FileMode.Create))
            using (BinaryWriter writer = new BinaryWriter(fs))
            {
                // 先写入记录总数,方便读取时知道要读多少条
                writer.Write(records.Count);

                foreach (varrecordinrecords)
                {
                    // 按固定顺序依次写入每个字段
                    writer.Write(record.DeviceId);
                    writer.Write(record.PressureValue);
                    writer.Write(record.TimestampTicks);
                }
            }

            Console.WriteLine($"已写入 {records.Count} 条记录到二进制文件");
        }

        // 用BinaryReader按写入时的顺序读回记录列表
        static List<PressureRecord> ReadRecordsFromBinary(string filePath)
        {
            List<PressureRecord> records = new List<PressureRecord>();

            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            using (BinaryReader reader = new BinaryReader(fs))
            {
                // 先读取记录总数
                int recordCount = reader.ReadInt32();

                for (int i = 0; i < recordCount; i++)
                {
                    // 严格按照写入顺序读取,顺序错了数据就乱了
                    PressureRecord record = new PressureRecord
                    {
                        DeviceId = reader.ReadString(),
                        PressureValue = reader.ReadDouble(),
                        TimestampTicks = reader.ReadInt64()
                    };
                    records.Add(record);
                }
            }

            return records;
        }
    }
}
图片

运行后,你会看到程序生成1000条模拟采集数据并写入二进制文件,再完整读回来验证,最后打印出文件大小——同样1000条数据,二进制文件比JSON格式能小出好几倍,速度也快得多。

🏭 工业实战小案例

场景: 化工厂需要把反应釜每秒采集的温度和压力数据持续追加写入二进制归档文件。

思路拆解:

  • • 用FileMode.Append模式打开文件流
  • • 每采集一条数据就立即写入
  • • 不需要一次性把所有历史数据加载进内存
  • • 用固定格式保证读取时能正确解析
using System;
using System.IO;

// 反应釜数据追加写入二进制文件
string archiveFilePath = @"\reactor_archive.bin";

void AppendReactorData(double reactorTemp, double reactorPressure)
{
    // Append模式打开,不覆盖已有内容
    using (FileStream fs = new FileStream(archiveFilePath, FileMode.Append))
    using (BinaryWriter writer = new BinaryWriter(fs))
    {
        writer.Write(DateTime.Now.Ticks);
        writer.Write(reactorTemp);
        writer.Write(reactorPressure);
    }
}

// 模拟连续采集多次数据
Random rnd = new Random();
for (int i = 0; i < 5; i++)
{
    double temp = 180 + rnd.NextDouble() * 20;
    double pressure = 2.5 + rnd.NextDouble() * 0.5;
    AppendReactorData(temp, pressure);
    Console.WriteLine($"已归档第{i + 1}条数据:温度{temp:F1},压力{pressure:F2}");
}

Console.WriteLine("反应釜数据持续追加写入完成");
图片

运行后,反应釜的每条采集数据都会被追加写入二进制归档文件,文件体积始终保持紧凑,即使连续运行几个月积累海量数据,占用空间也远比文本格式小得多。

⚠️ 这几个坑,我替你踩过了

❌ 还在网上抄 BinaryFormatter 的老教程代码 ✅ 一律用 BinaryWriter 和 BinaryReader 手动读写 📌 原因:BinaryFormatter存在严重安全漏洞,新版.NET已经彻底移除,用了直接编译不通过

❌ 写入顺序是 姓名→温度→时间,读取时写成 温度→姓名→时间 ✅ 读写顺序必须完全一致,建议写个常量或注释标注清楚顺序 📌 原因:二进制文件没有字段名标记,读取顺序错了数据会全部错位,还不一定报错,读出来的全是垂头脑

❌ 二进制文件读写时忘了加 using 释放资源 ✅ 用 using (FileStream fs = ...) using (BinaryWriter writer = ...) 嵌套包裹 📌 原因:文件流不释放会一直占用文件,高频写入场景下很容易导致文件被锁死打不开

📝 本节总结

学完本节,你掌握了:为什么工业高频采集数据适合用二进制存储而不是JSON/XML;BinaryWriter和BinaryReader怎么按固定顺序读写数据;还学会了追加写入二进制归档文件的实用做法。二进制存储是应对海量采集数据的关键武器,数据量越大,这一招的价值就越明显。


📖 本文是《C# 工业数字化应用开发专家》系列第 071 节 上一节:【XML 序列化与 XDocument 操作】 下一节:【集合进阶:Queue、Stack、LinkedList】(明天更新)

💬 你在工作中遇到过类似的需求吗? 欢迎在评论区说说你的场景,也许下一篇案例就来自你的留言。

🔔 还没关注的同学记得点击关注,系列课程持续更新, 学完这420节,从工厂小白到工业软件开发专家,我们一起走。


群贤毕至

访客