Как я должен перебрать двоичный файл в C ++? - PullRequest
3 голосов
/ 27 июня 2011

TL; DR

Что было бы хорошим способом, в C ++ и с использованием идиом STL, перебирать двоичный файл для чтения, преобразования, а затем снова записывать данные?Файлы могут быть довольно большими (несколько сотен МБ), поэтому я не хочу загружать весь файл в память за один раз.

Больше контекста

Япытаясь улучшить утилиту, которая выполняет различные операции с двоичными файлами.Эти файлы содержат набор записей, состоящий из заголовка и затем данных.Утилита предоставляет опции для вывода файла в текст, отфильтровывать определенные записи, извлекать определенные записи, добавлять записи и т. Д. К сожалению, все эти функции имеют код для чтения и записи из файла, скопированного и вставленного в каждую функцию, поэтому один исходный файлсодержит много избыточного кода и начинает выходить из-под контроля.

Я только начинаю понимать, как использовать 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"

Вывод
альфа
браво
чарли
дельта

Ответы [ 4 ]

3 голосов
/ 27 июня 2011

Я бы справился с этим, перегрузив 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_iterator и ostream_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());
2 голосов
/ 27 июня 2011

Вместо этого:

while(!input.eof())

Проще (и понятнее) написать:

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

Для создания магии шаблона вам нужно использовать std :: istream_iterator иstd :: ostream_iterator.

Для этого, как правило, требуется написать оператор >> и оператор << для вашего класса. </p>

PS.Я ненавижу использование бинарных объектов (RecordHeader).Это делает код сложнее поддерживать.Поток объекта так, чтобы он знал, как читать себя обратно. Что приводит к операторам >> и << </p>

1 голос
/ 27 июня 2011

Код, который вы опубликовали, и идея о классе-обёртке выглядит для меня как лучший способ сделать это с помощью STL.

Если вы хотите предоставить простые данные своей основной программе, вы можете взглянуть на boost::iostream. Он предоставляет несколько хороших способов реализовать фильтры «в» потоке (например, фильтр zlib) и, возможно, то, что вы ищете.

0 голосов
/ 27 июня 2011

Вы можете создавать свои собственные потоковые операторы 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...