正在分析文件中的二进制数据

Parsing binary data from file

本文关键字:二进制 数据 文件      更新时间:2023-10-16

并提前感谢您的帮助!

我正在学习C++。我的第一个项目是为我们在实验室使用的二进制文件格式编写一个解析器。我能够使用"fread"在Matlab中很容易地获得一个解析器,看起来这可能适用于我在C++中尝试做的事情。但从我读到的内容来看,使用ifstream似乎是推荐的方式。

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

第二,我如何使用ifstream来解决我的问题?以下是我要做的。我有一个二进制文件,其中包含一组结构化的int、float和64位int。总共有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 read命令将文件作为char*数组读取到内存中:

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

因此,根据我的理解,我现在有一个指向一个名为"缓冲区"的数组的指针。如果我调用缓冲区[0],我应该得到一个1字节的内存地址,对吧?(相反,我遇到了seg故障。(

我现在真正需要做的应该很简单。在执行了上面的ifstream代码之后,我应该有一个相当长的缓冲区,其中填充了一些1和0。我只想能够从内存中读取这些东西,一次32位,根据我目前正在处理的4字节块,转换为整数或浮点

例如,如果二进制文件包含N个288字节的数据块,那么我提取的每个数组都应该有N个成员。(除了最后一个数组,它将有64N个成员。(

由于我在内存中有二进制数据,所以我基本上只想从缓冲区中读取,一次读取一个32位的数字,并将结果值放入适当的数组中。

最后,我可以一次访问多个数组位置吗?(例如数组(3:5(->[1,2,1]对于数组=[3,4,1,2,1]

首先,使用iostreams,尤其是文件流的优势与资源管理有关。当自动文件流变量超出范围时,它们将被关闭并清理,而不必使用fclose手动清理。如果同一范围内的其他代码可以引发异常,那么这一点非常重要。

其次,解决这类问题的一种可能方法是以适当的方式简单地定义流插入和提取运算符。在这种情况下,因为您有一个复合类型,您需要帮助编译器,告诉它不要在类型中添加填充字节。以下代码应该适用于gcc和microsoft编译器。

#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;
}
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的优点,这只是一个抽象的问题。您可以用更方便的方式提取文件的内容。然后,您不必使用缓冲区,而是可以使用类创建结构,然后通过重载<lt;例如操作员。

您可能会为C++寻找序列化库。也许s11可能有用。

这个问题展示了如何将数据从缓冲区转换为特定类型。通常,您应该更喜欢使用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));
}

此代码将把整个文件读取到缓冲区中。接下来要做的是将数据写入valarrays(用于所需的选择(。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实现。