从文件中解析二进制数据

4

非常感谢您的帮助!

我正在学习C++。我的第一个项目是为实验室使用的二进制文件格式编写解析器。我已经在Matlab中使用"fread"轻松地编写了一个解析器,看起来这可能也适用于我在C++中尝试做的事情。但从我所读到的资料来看,似乎使用ifstream是推荐的方法。

我的问题有两个方面。首先,使用ifstream相比fread有什么优点?

其次,我该如何使用ifstream来解决我的问题?这就是我要做的事情。我有一个包含一组整数、浮点数和64位整数的结构化二进制文件。总共有8个数据字段,我想将每个字段读入其自己的数组中。

数据的结构如下,在重复的288字节块中:

Bytes 0-3: int
Bytes 4-7: int
Bytes 8-11: float
Bytes 12-15: float
Bytes 16-19: float
Bytes 20-23: float
Bytes 24-31: int64
Bytes 32-287: 64x float

我能够使用fstream读取文件并将其存储为char*数组。
char * buffer;
ifstream datafile (filename,ios::in|ios::binary|ios::ate);
datafile.read (buffer, filesize); // Filesize in bytes 

所以,从我的理解来看,我现在有一个指向名为“buffer”的数组的指针。如果我调用buffer [0],我应该得到一个1字节的内存地址,对吗?(但是,我却得到了段错误。)
现在我需要做的事情真的应该非常简单。在执行以上ifstream代码后,我应该有一个相当长的缓冲区,其中填充了许多1和0。我只想能够从内存中读取这些内容,每次读取32位,根据我当前正在处理的4字节块将其转换为整数或浮点数。
例如,如果二进制文件包含N个288字节的数据块,则我提取的每个数组都应该有N个成员。 (除了最后一个数组,它将有64N个成员。)
由于我已经将二进制数据存储在内存中,因此基本上我只想从缓冲区中一次读取一个32位数字,并将结果值放置在适当的数组中。
最后 - 我能否像Matlab那样同时访问多个数组位置?(例如,array(3:5)-> [1,2,1],对于array = [3,4,1,2,1])

2
有没有必要一次性读取整个文件,而不是只读取你预期的块? - Lionel
4个回答

3
首先,使用iostreams,特别是文件流的优点在于资源管理。自动文件流变量会在超出其作用域时关闭和清理,而不必手动使用fclose进行清理。如果同一作用域中的其他代码可能会抛出异常,则这很重要。
其次,解决此类问题的一种可能方法是简单地以适当的方式定义流插入和提取运算符。在这种情况下,因为你有一个复合类型,所以需要通过告诉编译器不在类型内部添加填充字节来帮助它。以下代码应该适用于gcc和微软编译器。
#pragma pack(1)
struct MyData
{
    int i0;
    int i1;
    float f0;
    float f1;
    float f2;
    float f3;
    uint64_t ui0;
    float f4[64];
};
#pragma pop(1)

std::istream& operator>>( std::istream& is, MyData& data ) {
    is.read( reinterpret_cast<char*>(&data), sizeof(data) );
    return is;
}

std::ostream& operator<<( std::ostream& os, const MyData& data ) {
    os.write( reinterpret_cast<const char*>(&data), sizeof(data) );
    return os;
}

1
关于字节序怎么处理?这段代码假设文件中的整数字节序与机器本地字节序相同。 - BarbaraKwarc

0
char * buffer;
ifstream datafile (filename,ios::in|ios::binary|ios::ate);
datafile.read (buffer, filesize); // Filesize in bytes 

在读取之前,您需要先分配一个缓冲区:

buffer = new filesize[filesize];
datafile.read (buffer, filesize);

关于 ifstream 的优点,它涉及到抽象的问题。您可以以更方便的方式抽象文件的内容。这样,您就不必使用缓冲区,而是可以使用类创建结构,然后通过重载 << 运算符来隐藏有关文件中存储方式的详细信息。

0

这个问题展示了如何将缓冲区中的数据转换为特定类型。一般来说,您应该优先使用std::vector<char>作为您的缓冲区。这样看起来会像这样:

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>

int main() {
    std::ifstream input("your_file.dat");
    std::vector<char> buffer;
    std::copy(std::istreambuf_iterator<char>(input),
              std::istreambuf_iterator<char>(),
              std::back_inserter(buffer));
}

这段代码将会把整个文件读入缓存。接下来你要做的是将你的数据写入到 valarray 中(根据你需要的选定)。valarray 大小不变,所以你必须能够预先计算出所需数组的大小。以下代码应该可以满足你的格式需求:

std::valarray array1(buffer.size()/288); // each entry takes up 288 bytes

然后您可以使用普通的for循环将元素插入到数组中:

for(int i = 0; i < buffer.size()/288; i++) {
    array1[i] = *(reinterpret_cast<int *>(buffer[i*288]));   // first position
    array2[i] = *(reinterpret_cast<int *>(buffer[i*288]+4)); // second position
}

请注意,在64位系统上,这个程序可能不会按照您的期望工作,因为整数会占用8个字节。这个问题解释了一些关于C++和类型大小的知识。
您可以使用valarray来实现您所描述的选择。

0

你可能正在寻找 C++ 的序列化库。也许 s11n 会有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接