有效地从文件中读取结构化二进制数据

问题描述 投票:1回答:4

我有以下代码片段,该片段读取二进制文件并对其进行验证:

 FileStream f = File.OpenRead("File.bin");
 MemoryStream memStream = new MemoryStream();
 memStream.SetLength(f.Length);
 f.Read(memStream.GetBuffer(), 0, (int)f.Length);
 f.Seek(0, SeekOrigin.Begin);
 var r = new BinaryReader(f);
 Single prevVal=0;
 do
 {
    r.ReadUInt32();
    var val = r.ReadSingle();
    if (prevVal!=0) {
       var diff = Math.Abs(val - prevVal) / prevVal;
       if (diff > 0.25)
          Console.WriteLine("Bad!");
    }
    prevVal = val;
 }
 while (f.Position < f.Length);

不幸的是,它的工作非常缓慢,我正在寻求改进。在C ++中,我只需将文件读入字节数组,然后将该数组重铸为结构数组:

struct S{
   int a;
   float b;
}

我将如何在C#中执行此操作?

c# optimization binaryfiles
4个回答
3
投票

用显式布局(struct)定义与您的C ++代码完全相同的readonly struct(可能是[StructLayout(LayoutKind.Explicit)]),然后是以下之一:

  1. 将文件作为内存映射文件打开,获取指向数据的指针;在原始指针上使用unsafe代码,或在数据上使用Unsafe.AsRef<YourStruct>,然后使用Unsafe.Add<>进行迭代
  2. 将文件作为内存映射文件打开,获取指向数据的指针;在create a custom memory的指针上单击T,并在范围上进行迭代
  3. byte[]格式打开文件;在Span<byte>上创建一个byte[],然后使用MemoryMarshal.Cast<,>创建一个Span<YourType>,并在其上进行迭代
  4. byte[]格式打开文件;使用fixed固定byte*并获得一个指针;使用unsafe代码遍历指针
  5. 有些东西涉及“管道”-一个作为缓冲区的Pipe,可能使用StreamConnection上的FileStream来填充管道,以及一个从管道中退出的辅助循环;并发症:缓冲区可能不连续,并可能在不方便的地方分割;可解决的,但只要第一个跨度不小于8个字节,就需要微妙的代码

(或这些概念的某种组合)

其中任何一个都应该像您的C ++版本一样工作。第四种很简单,但是对于非常大的数据,您可能希望使用内存映射文件


1
投票

这是我们使用的(与C#的较早版本兼容:]

public static T[] FastRead<T>(FileStream fs, int count) where T: struct
{
    int sizeOfT = Marshal.SizeOf(typeof(T));

    long bytesRemaining  = fs.Length - fs.Position;
    long wantedBytes     = count * sizeOfT;
    long bytesAvailable  = Math.Min(bytesRemaining, wantedBytes);
    long availableValues = bytesAvailable / sizeOfT;
    long bytesToRead     = (availableValues * sizeOfT);

    if ((bytesRemaining < wantedBytes) && ((bytesRemaining - bytesToRead) > 0))
    {
        Debug.WriteLine("Requested data exceeds available data and partial data remains in the file.");
    }

    T[] result = new T[availableValues];

    GCHandle gcHandle = GCHandle.Alloc(result, GCHandleType.Pinned);

    try
    {
        uint bytesRead;

        if (!ReadFile(fs.SafeFileHandle, gcHandle.AddrOfPinnedObject(), (uint)bytesToRead, out bytesRead, IntPtr.Zero))
        {
            throw new IOException("Unable to read file.", new Win32Exception(Marshal.GetLastWin32Error()));
        }

        Debug.Assert(bytesRead == bytesToRead);
    }

    finally
    {
        gcHandle.Free();
    }

    GC.KeepAlive(fs);

    return result;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1415:DeclarePInvokesCorrectly")]
[DllImport("kernel32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]

private static extern bool ReadFile
(
    SafeFileHandle       hFile,
    IntPtr               lpBuffer,
    uint                 nNumberOfBytesToRead,
    out uint             lpNumberOfBytesRead,
    IntPtr               lpOverlapped
);

注意:当然,这仅适用于仅包含可漂白类型的结构。并且必须使用[StructLayout(LayoutKind.Explicit)]并声明打包内容,以确保结构布局与文件中数据的二进制格式相同。

对于最新版本的C#,您可以使用Marc在其他答案中提到的Span


0
投票

您实际上实际上根本没有使用MemoryStream。您的BinaryReader直接访问文件。要让BinaryReader使用MemoryStream代替:

替换

f.Seek(0, SeekOrigin.Begin);
var r = new BinaryReader(f);

...

while (f.Position < f.Length);

memStream.Seek(0, SeekOrigin.Begin);
var r = new BinaryReader(memStream);

...

while(r.BaseStream.Position < r.BaseStream.Length)

0
投票

感谢大家提供非常有用的评论和答案。鉴于此输入,这是我的首选解决方案:

      [StructLayout(LayoutKind.Sequential, Pack = 1)]
      struct Data
      {
         public UInt32 dummy;
         public Single val;
      };
      static void Main(string[] args)
      {
         byte [] byteArray = File.ReadAllBytes("File.bin");
         ReadOnlySpan<Data> dataArray = MemoryMarshal.Cast<byte, Data>(new ReadOnlySpan<byte>(byteArray));
         Single prevVal=0;
         foreach( var v in dataArray) {
            if (prevVal!=0) {
               var diff = Math.Abs(v.val - prevVal) / prevVal;
               if (diff > 0.25)
                  Console.WriteLine("Bad!");
            }
            prevVal = v.val;
         }
      }
   }

确实比原始实现要快得多。

© www.soinside.com 2019 - 2024. All rights reserved.