我有这个需要处理的资源文件,它包含一组文件。
首先,资源文件列出了其中包含的所有文件,以及其他一些数据,例如在此结构中:
struct FileEntry{
byte Value1;
char Filename[12];
byte Value2;
byte FileOffset[3];
float whatever;
}
所以我需要读取这个大小的块。
我正在使用FileStream中的Read函数,但是如何指定struct的大小? 我用过:
int sizeToRead = Marshal.SizeOf(typeof(Header));
然后将此值传递给Read,但之后我只能读取一组byte [],我不知道如何转换为指定的值(我知道如何获取单字节值...但不是其他人。)
另外,我需要指定一个不安全的上下文,我不知道它是否正确......
在我看来,读取字节流比我在.NET中想象的更难:)
谢谢!
答案 0 :(得分:7)
假设这是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()
};
}
如果你坚持使用结构,你应该使你的结构不可变,并为每个字段创建一个带有参数的构造函数。
答案 1 :(得分:5)
如果您可以使用不安全的代码:
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关键字将数组嵌入struct中。由于它已修复,如果您经常创建这些 会导致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();
...
}
这种不安全的方式几乎是即时的,速度要快得多,特别是当你一次转换很多结构时。问题是你想使用不安全的。如果您绝对需要性能提升,我的建议只使用不安全的方法。
答案 2 :(得分: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);
}
答案 3 :(得分:2)
使用FileStream
包裹BinaryReader
将为您提供基本类型的专用Read*()
方法:
http://msdn.microsoft.com/en-us/library/system.io.binaryreader.aspx
在我的脑海中,您可能会用struct
标记[StructLayout(LayoutKind.Sequential)]
(以确保在内存中正确表示)并使用unsafe
块中的指针实际填充结构C-样式。如果你真的不需要它(互操作,像图像处理这样繁重的操作等),不建议使用unsafe
。
答案 4 :(得分:0)
不是一个完整的答案(我认为它已被覆盖),但有关文件名的具体说明:
Char
类型可能不是C#中的单字节内容,因为.Net字符是unicode,这意味着它们支持的字符值远远超过255,因此将文件名数据解释为Char[]
数组将给出问题。因此,第一步肯定是将其视为Byte[12]
,而不是Char[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文本编码)访问,您可以使用{{{ 1}}。