首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 讀取二進位檔案與判斷檔案結束的正確姿勢

     編輯:關於C#

錯誤示範:Try-Catch 控制流程


// 不推薦:用例外處理判斷 EOF
var fs = File.OpenRead(path);
var br = new BinaryReader(fs);
try
{
    while (true)
    {
        var b = br.ReadByte(); // 結尾拋出 EndOfStreamException
        // 處理 b
    }
}
catch (EndOfStreamException)
{
    // 正常結束
}
finally { br.Close(); }

缺點:例外處理成本高、語意不清、掩蓋真正錯誤。

正確做法 1:比較 Position 與 Length


using (var fs = File.OpenRead(path))
using (var br = new BinaryReader(fs))
{
    while (br.BaseStream.Position < br.BaseStream.Length)
    {
        var b = br.ReadByte();
        // 或讀取結構化資料
        // var i = br.ReadInt32();
        // var s = br.ReadString();
    }
}
  • 適用:可搜尋資料流(檔案、MemoryStream)
  • 原理:Position 為當前指標,Length 為總長度

正確做法 2:PeekChar() 非封鎖預覽


using (var fs = File.OpenRead(path))
using (var br = new BinaryReader(fs))
{
    while (br.PeekChar() > -1)
    {
        var b = br.ReadByte();
        // 處理
    }
}
  • PeekChar() 回傳下一個字元(不消耗),結尾回傳 -1
  • 同樣要求資料流可搜尋
  • 注意:BinaryReader 內部緩衝可能導致 PeekChar 與實際讀取位置不一致,建議搭配 ReadByte 使用

進階:結構化二進位讀取


[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct FileHeader
{
    public uint Magic;      // 4 bytes
    public ushort Version;  // 2 bytes
    public ushort Count;    // 2 bytes
    // 總計 8 bytes
}

public static FileHeader ReadHeader(string path)
{
    using (var fs = File.OpenRead(path))
    using (var br = new BinaryReader(fs))
    {
        if (fs.Length < 8) throw new InvalidDataException("檔案太小");
        
        var header = new FileHeader
        {
            Magic = br.ReadUInt32(),
            Version = br.ReadUInt16(),
            Count = br.ReadUInt16()
        };
        return header;
    }
}

大檔案/記憶體對應檔案


// 記憶體對應檔案(適合超大檔、隨機存取)
using (var mmf = MemoryMappedFile.CreateFromFile(path, FileMode.Open, null, 0, MemoryMappedFileAccess.Read))
using (var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read))
{
    var header = accessor.Read(0);
    // 隨機讀取第 N 筆記錄
    long offset = 8 + N * RecordSize;
    var record = accessor.Read(offset);
}

非搜尋資料流(NetworkStream、管道)


// 無法使用 Position/Length/PeekChar
// 必須依協定讀取:長度前綴、分隔符、或固定大小
using (var ns = new NetworkStream(socket))
using (var br = new BinaryReader(ns, Encoding.UTF8, leaveOpen: true))
{
    while (true)
    {
        // 範例:長度前綴協定
        if (!TryReadInt32(br, out int len)) break; // 連線關閉或錯誤
        if (len < 0 || len > MaxMessageSize) throw new ProtocolException();
        
        var data = br.ReadBytes(len);
        if (data.Length != len) throw new EndOfStreamException();
        
        ProcessMessage(data);
    }
}

bool TryReadInt32(BinaryReader br, out int value)
{
    var buffer = new byte[4];
    int totalRead = 0;
    while (totalRead < 4)
    {
        int read = br.BaseStream.Read(buffer, totalRead, 4 - totalRead);
        if (read == 0) { value = 0; return false; } // EOF
        totalRead += read;
    }
    value = BitConverter.ToInt32(buffer, 0);
    return true;
}

總結選擇指南

資料流類型判斷 EOF 方法備註
FileStream, MemoryStreamPosition < Length 或 PeekChar() > -1可搜尋,效能最佳
MemoryMappedFile直接依 offset 讀取,無需迴圈判斷 EOF隨機存取、大檔案首選
NetworkStream, PipeStream協定層面處理(長度前綴/分隔符)不可搜尋,不可用 Position/Length
GZipStream, CryptoStream嘗試讀取,回傳 0 表示結束ReadByte() == -1 或 Read(buffer,...) == 0

核心原則:依資料流特性選擇正確的 EOF 判斷方式,拒絕用例外控制流程,讓程式碼語意清晰、效能最佳。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved