从文件中解析二进制数据

时间:2011-10-24 04:54:15

标签: c++ parsing binary char

并提前感谢您的帮助!

我正在学习C ++。我的第一个项目是为我们实验室使用的二进制文件格式编写解析器。我能够使用" fread"在Matlab中相当容易地使用解析器,看起来这可能适用于我在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和0的数字。我只是希望能够从内存中读取这些内容,一次32位,根据我目前正在处理的4字节块进行整数或浮动。

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

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

最后 - 我可以一次访问多个阵列位置,一个Matlab吗? (例如array(3:5) - > [1,2,1] for array = [3,4,1,2,1])

4 个答案:

答案 0 :(得分:3)

首先,使用iostream,特别是文件流的优势与资源管理有关。自动文件流变量将在超出范围时关闭并清除,而不必使用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;
}

答案 1 :(得分: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的优点,这是一个抽象的问题。您可以以更方便的方式抽象文件的内容。然后,您不必使用缓冲区,而是可以使用类创建结构,然后通过重载&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;例如运营商。

答案 2 :(得分:0)

您可能正在寻找C ++的序列化库。也许s11n可能有用。

答案 3 :(得分:0)

This question显示了如何将数据从缓冲区转换为特定类型。通常,您应该更喜欢使用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 s(用于你想要的选择)。 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个字节。 This question解释了一些关于C ++和类型大小的内容。

您在此处描述的选择可以使用valarray来实现。