ZetCode

C# BinaryReader

最后修改于 2025 年 4 月 20 日

本教程探讨了 C# BinaryReader 类,用于从文件或流中读取二进制数据。它涵盖了各种场景,从基本数据读取到高级技术,如处理结构和自定义编码。

BinaryReader 类是 System.IO 命名空间的一部分,它以特定的编码从二进制流中读取基本数据类型(例如,int、double、string)。它可以与 BinaryWriter 无缝协作来写入二进制数据。

BinaryReader 非常适合处理二进制文件,例如配置文件、序列化对象或自定义数据格式。本教程通过实际示例演示了它的用法,这些示例突出了不同的读取技术。

基本的 BinaryReader 示例

此示例展示了如何从使用 BinaryWriter 创建的二进制文件中读取基本数据类型,演示了 BinaryReader 的基本功能。

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        // Write data first
        using (var writer = new BinaryWriter(File.Open("data.bin", FileMode.Create)))
        {
            writer.Write(42);
            writer.Write(3.14);
            writer.Write(true);
            writer.Write("Hello Binary");
        }

        // Read data back
        using (var reader = new BinaryReader(File.Open("data.bin", FileMode.Open)))
        {
            int number = reader.ReadInt32();
            double pi = reader.ReadDouble();
            bool flag = reader.ReadBoolean();
            string text = reader.ReadString();

            Console.WriteLine($"Int: {number}");
            Console.WriteLine($"Double: {pi}");
            Console.WriteLine($"Boolean: {flag}");
            Console.WriteLine($"String: {text}");
        }
    }
}

该程序使用 BinaryWriter 将整数、双精度浮点数、布尔值和字符串写入二进制文件。然后,BinaryReader 使用特定于类型的方法(如 ReadInt32、ReadDouble、ReadBoolean 和 ReadString)读取这些值。

读取顺序必须与写入顺序匹配,才能正确解释二进制数据。using 语句通过自动关闭文件流来确保适当的资源管理。此示例非常适合理解 BinaryReader 用于简单二进制文件操作的核心功能。

从二进制文件读取数组

此示例演示了如何从二进制文件中读取整数数组,展示了如何使用 BinaryReader 处理基本类型的集合。

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        // Write array first
        int[] numbers = { 1, 2, 3, 4, 5 };
        using (var writer = new BinaryWriter(File.Open("array.bin", FileMode.Create)))
        {
            writer.Write(numbers.Length);
            foreach (var num in numbers)
            {
                writer.Write(num);
            }
        }

        // Read array back
        using (var reader = new BinaryReader(File.Open("array.bin", FileMode.Open)))
        {
            int length = reader.ReadInt32();
            int[] readNumbers = new int[length];
            
            for (int i = 0; i < length; i++)
            {
                readNumbers[i] = reader.ReadInt32();
            }

            Console.WriteLine("Read array: " + string.Join(", ", readNumbers));
        }
    }
}

BinaryWriter 写入数组长度,然后写入每个整数元素。此元数据(长度)对于 BinaryReader 在读取时分配正确的数组大小至关重要。读取器首先使用 ReadInt32 检索长度,然后迭代读取每个整数。

这种方法确保了数组的精确重建。它对于处理二进制文件中的集合非常有用,例如保存分数列表或坐标。该示例突出了包含元数据来描述数据结构的重要性。

读取混合数据类型

此示例展示了如何读取包含混合数据类型的二进制文件,演示了 BinaryReader 处理结构化数据的能力。

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        // Write mixed data
        using (var writer = new BinaryWriter(File.Open("mixed.bin", FileMode.Create)))
        {
            writer.Write("Product");
            writer.Write(12345);
            writer.Write(19.99m);
            writer.Write(DateTime.Now.Ticks);
        }

        // Read mixed data
        using (var reader = new BinaryReader(File.Open("mixed.bin", FileMode.Open)))
        {
            string name = reader.ReadString();
            int id = reader.ReadInt32();
            decimal price = reader.ReadDecimal();
            DateTime date = new DateTime(reader.ReadInt64());

            Console.WriteLine($"Name: {name}");
            Console.WriteLine($"ID: {id}");
            Console.WriteLine($"Price: {price:C}");
            Console.WriteLine($"Date: {date:g}");
        }
    }
}

BinaryWriter 按顺序写入字符串、整数、十进制数和 DateTime(作为刻度)。BinaryReader 分别使用 ReadStringReadInt32ReadDecimalReadInt64 读取这些值。DateTime 从其刻度重建以实现精确恢复。

保持读取的精确顺序和类型对于避免数据误解至关重要。此示例适用于读取结构化二进制文件的应用程序,例如产品目录或日志条目,其中多种数据类型组合在单个文件中。

处理文件结束

此示例演示了如何使用 BaseStream 属性读取二进制数据直到文件结束,以避免异常。

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        // Write unknown number of items
        using (var writer = new BinaryWriter(File.Open("items.bin", FileMode.Create)))
        {
            Random rand = new Random();
            int count = rand.Next(5, 15);
            
            for (int i = 0; i < count; i++)
            {
                writer.Write(rand.Next(100));
            }
        }

        // Read until EOF
        using (var reader = new BinaryReader(File.Open("items.bin", FileMode.Open)))
        {
            Console.WriteLine("Reading numbers:");
            
            while (reader.BaseStream.Position < reader.BaseStream.Length)
            {
                int num = reader.ReadInt32();
                Console.Write(num + " ");
            }
            
            Console.WriteLine();
        }
    }
}

BinaryWriter 将随机数量的整数写入文件。BinaryReader 使用 while 循环读取整数,只要流的当前位置 (BaseStream.Position) 小于其总长度 (BaseStream.Length)。

此方法可防止读取超出文件末尾,这将引发异常。它对于处理具有未知元素数量的二进制文件非常有用,例如日志文件或数据流,其中未预定义总大小。

使用编码读取字符串

此示例展示了如何使用 BinaryReader 读取具有不同编码的字符串,从而处理默认和自定义编码格式。

Program.cs
using System;
using System.IO;
using System.Text;

class Program
{
    static void Main()
    {
        // Write strings with different encodings
        using (var writer = new BinaryWriter(File.Open("strings.bin", FileMode.Create)))
        {
            writer.Write("ASCII text");
            writer.Write(Encoding.UTF32.GetBytes("UTF-32 text"));
        }

        // Read with specific encodings
        using (var reader = new BinaryReader(File.Open("strings.bin", FileMode.Open)))
        {
            string asciiText = reader.ReadString();
            Console.WriteLine("ASCII: " + asciiText);

            byte[] utf32Bytes = reader.ReadBytes(44); // 11 chars * 4 bytes
            string utf32Text = Encoding.UTF32.GetString(utf32Bytes);
            Console.WriteLine("UTF-32: " + utf32Text);
        }
    }
}

BinaryWriter 写入 UTF-8 字符串(Write(string) 的默认值)和 UTF-32 字符串作为原始字节。BinaryReader 使用 ReadString 读取 UTF-8 字符串,并通过读取 44 个字节(11 个字符 × 4 个字节)并使用 Encoding.UTF32.GetString 转换它们来读取 UTF-32 字符串。

这种方法对于处理具有非标准编码的二进制文件至关重要,例如由旧系统或特定协议生成的文件。它演示了 BinaryReader 在未使用默认 UTF-8 编码时读取字符串的灵活性。

从二进制文件读取结构体

此高级示例展示了如何使用不安全的代码和内存封送将结构化的二进制数据读取到 C# 结构体中,以实现精确的数据映射。

Program.cs
using System;
using System.IO;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Product
{
    public int Id;
    public float Price;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
    public string Name;
}

class Program
{
    static unsafe void Main()
    {
        // Write sample product
        using (var writer = new BinaryWriter(File.Open("product.bin", FileMode.Create)))
        {
            writer.Write(123); // ID
            writer.Write(19.99f); // Price
            writer.Write("Premium Coffee".PadRight(20, '\0')); // Name
        }

        // Read into struct
        using (var reader = new BinaryReader(File.Open("product.bin", FileMode.Open)))
        {
            byte[] buffer = reader.ReadBytes(Marshal.SizeOf(typeof(Product)));
            
            fixed (byte* ptr = buffer)
            {
                Product product = Marshal.PtrToStructure<Product>((IntPtr)ptr);
                Console.WriteLine($"ID: {product.Id}");
                Console.WriteLine($"Price: {product.Price:C}");
                Console.WriteLine($"Name: {product.Name.Trim('\0')}");
            }
        }
    }
}

Product 结构体使用带有 LayoutKind.Sequential 和 Pack=1 的 StructLayout 来确保字段与二进制文件的格式完全对齐。BinaryWriter 写入结构体的数据(ID、价格和固定长度的名称)。BinaryReader 读取结构体的精确字节大小,该大小由 Marshal.SizeOf 确定。

字节使用 Marshal.PtrToStructure 封送到 Product 实例中,使用不安全的代码来固定字节数组。这种技术对于读取复杂的二进制格式非常强大,例如旧系统或自定义协议中的二进制格式,在这些格式中,数据被紧密地打包而没有填充。

从 MemoryStream 读取二进制数据

此示例演示了使用 BinaryReader 从 MemoryStream 读取二进制数据,展示了其在非文件流中的多功能性。

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        // Create binary data in memory
        byte[] data = new byte[16];
        new Random().NextBytes(data);

        // Read from MemoryStream
        using (var stream = new MemoryStream(data))
        using (var reader = new BinaryReader(stream))
        {
            Console.WriteLine("First int: " + reader.ReadInt32());
            Console.WriteLine("Second int: " + reader.ReadInt32());
            Console.WriteLine("Remaining bytes: " + 
                BitConverter.ToString(reader.ReadBytes(8)));
        }
    }
}

使用 16 字节的随机数据数组初始化 MemoryStreamBinaryReader 将此流视为文件进行读取,使用 ReadInt32 提取两个 32 位整数,并使用 ReadBytes 提取剩余的 8 个字节。字节使用 BitConverter.ToString 格式化为十六进制字符串以进行显示。

这种方法对于处理内存中的二进制数据非常有用,例如网络数据包、序列化对象或来自 API 的数据。BinaryReader 与任何 Stream 一起使用的能力使其在文件 I/O 之外的场景中具有通用性,从而可以在不同的源之间实现一致的二进制数据处理。

使用 BinaryReader 进行错误处理

此示例展示了如何在读取二进制数据时处理错误,从而确保使用 try-catch 块和流验证进行可靠的文件处理。

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        // Write partial data
        using (var writer = new BinaryWriter(File.Open("partial.bin", FileMode.Create)))
        {
            writer.Write(42);
            writer.Write("Incomplete");
            // Intentionally omit a double
        }

        // Read with error handling
        using (var reader = new BinaryReader(File.Open("partial.bin", FileMode.Open)))
        {
            try
            {
                int number = reader.ReadInt32();
                string text = reader.ReadString();
                double value;

                // Check if enough bytes remain for a double
                if (reader.BaseStream.Length - reader.BaseStream.Position >= 8)
                {
                    value = reader.ReadDouble();
                    Console.WriteLine($"Double: {value}");
                }
                else
                {
                    Console.WriteLine("Incomplete data: missing double value");
                }

                Console.WriteLine($"Int: {number}");
                Console.WriteLine($"String: {text}");
            }
            catch (EndOfStreamException ex)
            {
                Console.WriteLine($"Error: Reached end of file unexpectedly. {ex.Message}");
            }
            catch (IOException ex)
            {
                Console.WriteLine($"IO Error: {ex.Message}");
            }
        }
    }
}

BinaryWriter 写入一个整数和一个字符串,但省略一个双精度浮点数以模拟不完整的数据。BinaryReader 尝试读取整数、字符串和双精度浮点数,使用 try-catch 块来处理潜在的 EndOfStreamExceptionIOException 错误。

在读取双精度浮点数之前,程序会检查流中是否剩余足够的字节,以防止出现异常。此示例演示了用于二进制文件读取的强大错误处理,这对于处理可能已损坏或不完整的文件的应用程序至关重要,例如日志分析器或数据恢复工具。

来源

C# BinaryReader - 参考

在本文中,我们探讨了使用 BinaryReader 在 C# 中读取二进制数据。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有超过十年的经验。

列出所有 C# 教程