【问题标题】:How to read byte blocks into struct如何将字节块读入结构
【发布时间】:2011-10-18 14:43:07
【问题描述】:

我有这个需要处理的资源文件,其中包含一组文件。

首先,资源文件列出了其中包含的所有文件,以及一些其他数据,例如在这个结构中:

struct FileEntry{
     byte Value1;
     char Filename[12];
     byte Value2;
     byte FileOffset[3];
     float whatever;
}

所以我需要读取这个大小的块。

我正在使用 FileStream 中的读取函数,但如何指定结构的大小? 我用过:

int sizeToRead = Marshal.SizeOf(typeof(Header));

然后将此值传递给Read,但是我只能读取一组我不知道如何转换为指定值的字节[](我确实知道如何获取单字节值...但不是其他人)。

另外我需要指定一个不安全的上下文,我不知道它是否正确......

在我看来,读取字节流比我在 .NET 中想象的要难:)

谢谢!

【问题讨论】:

标签: c# .net struct filestream


【解决方案1】:

假设这是 C#,我不会将结构创建为 FileEntry 类型。我会用字符串替换 char[20] 并使用 BinaryReader - http://msdn.microsoft.com/en-us/library/system.io.binaryreader.aspx 来读取各个字段。您必须按照写入数据的顺序读取数据。

类似:

class FileEntry {
     byte Value1;
     char[] Filename;
     byte Value2;
     byte[] FileOffset;
     float whatever;
}

  using (var reader = new BinaryReader(File.OpenRead("path"))) {
     var entry = new FileEntry {
        Value1 = reader.ReadByte(),
        Filename = reader.ReadChars(12) // would replace this with string
        FileOffset = reader.ReadBytes(3),
        whatever = reader.ReadFloat()           
     };
  }

如果你坚持要有一个结构,你应该让你的结构不可变,并为你的每个字段创建一个带有参数的构造函数。

 

【讨论】:

  • 这就像一个魅力。您将如何“用字符串替换它”?使用 ReadString() 您不能指定大小,因此它会超出所需位置。
  • 实际上,如果之前写成字符串,字符串的大小是包含在流中的。来自 MSDN -“从当前流中读取字符串。字符串以长度为前缀,编码为整数,一次七位。” (msdn.microsoft.com/en-us/library/…)。但是,您还必须在此之前使用 BinaryWriter.Write(string) 。您可以使用 chars 构造一个字符串 - “StringField = new string(reader.ReadChars(20));”。
【解决方案2】:

不是一个完整的答案(我认为它已经被覆盖了),而是关于文件名的具体说明:

Char 类型在 C# 中可能不是单字节的,因为 .Net 字符是 unicode,这意味着它们支持的字符值远远超过 255,因此将文件名数据解释为 Char[] 数组会出现问题。所以第一步肯定是把它读成Byte[12],而不是Char[12]

也不建议从字节数组直接转换为字符数组,因为在这样的二进制索引中,比允许的 12 个字符 的文件名可能会用 0 字节填充,因此直接转换将产生一个始终为 12 个字符长的字符串,并且可能以这些零个字符结尾。

但是,不建议简单地删除这些零,因为读取此类数据的系统通常只是读取到第一个遇到的零,并且数组中的数据后面实际上可能包含垃圾,如果在将字符串放入缓冲区之前,写入系统不会费心专门用零清理缓冲区。这是很多程序都懒得做的事情,因为他们假设阅读系统无论如何只会解释字符串到第一个零。

所以,假设这确实是一个典型的以零结尾(C 风格)的字符串,以每字符一个字节的文本编码(如 ASCII 或 Win-1252)保存,第二步是切断第一个零上的字符串。您可以使用 Linq 的 TakeWhile 函数轻松完成此操作。然后第三步也是最后一步是将生成的字节数组转换为字符串,其中每个字符一个字节的文本编码恰好是:

public String StringFromCStringArray(Byte[] readData, Encoding encoding)
{
    return encoding.GetString(readData.TakeWhile(x => x != 0).ToArray());
}

正如我所说,编码可能类似于纯 ASCII,可以从 Encoding.ASCII 访问,或者 Windows-1252,标准的美国/西欧 Windows 文本编码,您可以使用 Encoding.GetEncoding("Windows-1252") 检索。

【讨论】:

    【解决方案3】:

    基于this article,只有我把它变成了通用的,这就是如何将数据直接编组到结构中。对于较长的数据类型非常有用。

    public static T RawDataToObject<T>(byte[] rawData) where T : struct
    {
        var pinnedRawData = GCHandle.Alloc(rawData,
                                           GCHandleType.Pinned);
        try
        {
            // Get the address of the data array
            var pinnedRawDataPtr = pinnedRawData.AddrOfPinnedObject();
    
            // overlay the data type on top of the raw data
            return (T) Marshal.PtrToStructure(pinnedRawDataPtr, typeof(T));
        }
        finally
        {
            // must explicitly release
            pinnedRawData.Free();
        }
    }
    

    示例用法:

    [StructLayout(LayoutKind.Sequential)]
    public struct FileEntry
    {
        public readonly byte Value1;
    
        //you may need to play around with this one
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
        public readonly string Filename;
    
        public readonly byte Value2;
    
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public readonly byte[] FileOffset;
    
        public readonly float whatever;
    }
    
    private static void Main(string[] args)
    {
        byte[] data =;//from file stream or whatever;
        //usage
        FileEntry entry = RawDataToObject<FileEntry>(data);
    }
    

    【讨论】:

    • 是的,字符串可能最好读为Byte[],然后使用某种方法将其转换为Char[],然后再转换为String。通过一些基本的“仅 ascii”检查和经典的 end-on-0 c-string 行为,就像String filename = new String(filenameArr.TakeWhile(x =&gt; x != 0).Select(x =&gt; x &lt; 128 ? Convert.ToChar(x) : '?').ToArray());
    【解决方案4】:

    如果你可以使用不安全的代码:

    unsafe struct FileEntry{
         byte Value1;
         fixed char Filename[12];
         byte Value2;
         fixed byte FileOffset[3];
         float whatever;
    }
    
    public unsafe FileEntry Get(byte[] src)
    {
         fixed(byte* pb = &src[0])
         {
             return *(FileEntry*)pb;
         } 
    }
    

    fixed 关键字将数组嵌入到结构中。由于它是固定的,如果您不断地创建它们并且从不放过它们,这可能会导致 GC 问题。请记住,常数大小是 n*sizeof(t)。所以 Filename[12] 分配了 24 个字节(每个 char 是 2 个字节的 unicode), FileOffset[3] 分配了 3 个字节。如果您不处理磁盘上的 unicode 数据,这很重要。我建议将其更改为 byte[] 并将结构转换为可以转换字符串的可用类。

    如果你不能使用 unsafe,你可以做整个 BinaryReader 方法:

    public unsafe FileEntry Get(Stream src)
    {
         FileEntry fe = new FileEntry();
         var br = new BinaryReader(src);
         fe.Value1 = br.ReadByte();
         ...
    }
    

    不安全的方法几乎是即时的,速度更快,尤其是当您一次转换大量结构时。问题是你想使用不安全的。我的建议是仅在您绝对需要性能提升时使用不安全的方法。

    【讨论】:

    • 那么不使用 unsafe 可能会更聪明,因为它只是处理率不会真正明显的小文件。
    【解决方案5】:

    BinaryReader 包装你的FileStream 将为您提供专用于原始类型的Read*() 方法: http://msdn.microsoft.com/en-us/library/system.io.binaryreader.aspx

    在我看来,您可能可以用[StructLayout(LayoutKind.Sequential)] 标记您的struct(以确保在内存中正确表示)并使用unsafe 块中的指针来实际填充结构C 样式。但是,如果您真的不需要它(互操作、图像处理等繁重的操作等),不建议使用 unsafe

    【讨论】:

      猜你喜欢
      • 2010-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-01
      • 1970-01-01
      相关资源
      最近更新 更多