我应该如何在c ++中迭代二进制文件?

时间:2011-06-27 17:33:09

标签: c++ stl

TL; DR

在C ++中使用STL惯用法迭代二进制文件进行读取,转换,然后再次写出数据会有什么好方法?这些文件可能非常大(几百MB),所以我不想一次将整个文件加载到内存中。

更多背景

我正在尝试改进对二进制文件执行各种操作的实用程序。这些文件包含一组记录,包括标题和数据。该实用程序提供了将文件转储为文本,过滤掉某些记录,提取某些记录,追加记录等的选项。不幸的是,所有这些函数都具有从文件中读取和写入的代码,并将其复制并粘贴到每个函数中,因此单个源文件包含大量冗余代码,并开始失控。

我只是刚刚开始使用C ++和STL,但这似乎应该可以使用某种模板/迭代器魔法,但我找不到解释这种情况的好例子。我可能追求的另一个策略是将文件访问包装在提供GetNextRecord和WriteNextRecord方法的类中。

以下是我正在处理的自包含/(极其)简化版本。是否有一种很好的方法来编写函数来读取WriteMyDataFile创建的文件中的数据并创建一个新的输出文件,删除包含“i”字符的所有记录?我希望抽象出文件的读/写,以便该函数主要用于处理数据。

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

const int c_version = 1;

struct RecordHeader
{
    int length;      
    int version;
};

void WriteMyDataFile(char* recordFile, char* data)
{
    ofstream output (recordFile, ios::out | ios::binary);

    stringstream records(data);

    while(records)
    {
        string r;
        records >> r;

        if(r.length() < 1)
        {
            continue;
        }

        RecordHeader header;
        header.length = r.length();
        header.version = c_version;

        output.write((char*)&header, sizeof(header));
        output.write(r.data(), header.length);
    }

    output.close();
}

 vector<string> ReadDataFile(char* recordFile)
 {
    vector<string> records;
    ifstream input (recordFile, ios::in | ios::binary);

    while(!input.eof())
    {
        RecordHeader header;
        input.read((char*)&header, sizeof(header));

        if(!input.eof())
        {
            char* buffer = new char[header.length + 1];

            input.read(buffer, header.length);
            buffer[header.length] = '\0';

            string s(buffer);
            records.push_back(s);

            delete[] buffer;
        }
    }
    return records;
}


int main(int argc, char *argv[])
{
    WriteMyDataFile(argv[1], argv[2]);
    vector<string> records = ReadDataFile(argv[1]);

    for(int i=0; i < records.size(); i++)
    {
        cout << records[i] << endl;
    }

    return 0;
}

运行此:

C:\>RecordUtility.exe test.bin "alpha bravo charlie delta"

输出:
    阿尔法
    布拉沃
    查理
    delta

4 个答案:

答案 0 :(得分:3)

我会通过为您的记录类型重载operator>>operator<<来解决此问题:

struct Record { 
    struct header {
        int length;
        int version;
    }

    header h;
    std::vector<char> body;
};

std::istream &operator>>(std::istream &is, Record &r) {
    is.read((char *)&r.h, sizeof(r.h));
    body.resize(h.length);
    is.read(&body[0], h.length);
    return is;
}

std::ostream &operator<<(std::ostream &os, Record const &r) { 
    os.write((char *)r.h, sizeof(r.h));
    os.write(r.body, r.body.size());
    return OS;
}

完成后,您可以将istream_iteratorostream_iterator与这些结构的流一起使用。例如,要复制大致相当于上述内容的副本,就像:

std::ifstream in("some input file");

std::copy(std::istream_iterator<Record>(in), 
          std::istream_iterator<Record>(),
          std::ostream_iterator<Record>(std::cout, "\n"));

或者,例如,如果您只想复制版本号为2或更高的记录,您可以执行以下操作:

struct filter { // or use a lambda in C++0x
    bool operator()(Record const &r) { return r.h.Version < 2; }
};

std::remove_copy_if(std::istream_iterator<Record>(in),
                    std::istream_iterator<Record>(),
                    std::ostream_iterator<Record>(std::cout, "\n"),
                    filter());

答案 1 :(得分:2)

而不是:

while(!input.eof())

写起来更容易(也更清晰):

RecordHeader header;
while(input.read((char*)&header, sizeof(header)))
{

要做模板魔术你想要的是我们std :: istream_iterator和std :: ostream_iterator。

这基本上要求你编写运算符&gt;&gt;和运算符&lt;&lt;为你的班级。

PS。我讨厌使用二进制对象(RecordHeader)。它使代码更难维护。流式传输对象,使其知道如何重新读回自己。这导致回到运算符&gt;&gt;和&lt;&lt;

答案 2 :(得分:1)

您发布的代码以及您对包装类的看法,对我来说,这似乎是使用STL执行此操作的最佳方式。

如果要向主程序提供纯数据,可以查看boost::iostream。它提供了一些很好的方法来实现过滤器“进入”流(例如zlib过滤器)以及您正在寻找的内容。

答案 3 :(得分:0)

您可以创建自己的流操作符operator<<operator>>来管理从流中读取/写入Record结构。然后你可以通过你的记录向量运行事物,应用你想要的任何过滤(可能用std::remove_if作为问题中的例子)并将其写回类似于下面...

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

namespace {
    template <class Type>
    void WriteBinary(const Type& data, std::ostream& os)
    {
        const char *binaryData = reinterpret_cast<const char*>(&data);
        os.write(binaryData, sizeof(data));
    }

    template <class Type>
    Type ReadBinary(std::istream& is)
    {
        Type data;
        is.read(reinterpret_cast<char*>(&data), sizeof(data));
        return data;
    }
}

struct Record
{
    int               mVersion;
    std::vector<char> mData;
};

std::ostream& operator<<(std::ostream& os, const Record& record)
{
    WriteBinary(record.mData.size(), os);
    WriteBinary(record.mVersion, os);

    std::copy(record.mData.begin(), 
              record.mData.end(), 
              std::ostream_iterator<char>(os)); 

    return os;
}

std::istream& operator>>(std::istream& is, Record& record)
{
    if (std::char_traits<char>::not_eof(is.peek()))
    {
        typedef std::vector<char>::size_type size_type;

        size_type length = ReadBinary<size_type>(is);
        record.mVersion = ReadBinary<int>(is);

        if (record.mVersion != 1)
        {
            throw std::runtime_error("Invalid version number.");
        }

        record.mData.clear();
        record.mData.resize(length);
        is.read(&record.mData.front(), length);
    }
    else
    {
        // Read the EOF char to invalidate the stream.
        is.ignore();
    }

    return is;
}

int main()
{
    // Create a Record
    std::string str = "Hello";

    Record rec;
    rec.mVersion = 1;
    rec.mData.assign(str.begin(), str.end());

    // Write two copies of the record to the stream.
    std::stringstream ss;
    ss << rec << rec;

    // Read all the records in the "file"
    std::vector<Record> records((std::istream_iterator<Record>(ss)),
                                std::istream_iterator<Record>());

    std::cout << "Read " << records.size() << " records." << std::endl;

    // Manipulate records here...then write all of them back to a file.
    std::stringstream myNewFile;
    std::copy(records.begin(), 
              records.end(), 
              std::ostream_iterator<Record>(myNewFile));

    return 0;
}