Разбор двоичных данных из файла - PullRequest
4 голосов
/ 24 октября 2011

и заранее благодарю за помощь!

Я нахожусь в процессе изучения 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

Я могу прочитать файл в память в виде массива char * с помощью команды чтения fstream:

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

Итак, насколько я понимаю, теперь у меня есть указатель на массив, называемый «буфер». Если бы мне нужно было вызвать буфер [0], я бы получил 1-байтовый адрес памяти, верно? (Вместо этого я получаю ошибку сегмента.)

То, что мне сейчас нужно сделать, должно быть очень простым. После выполнения вышеупомянутого кода ifstream у меня должен быть довольно длинный буфер, заполненный числом 1 и 0. Я просто хочу иметь возможность читать эти вещи из памяти, 32-битные за раз, приводить их как целые числа или числа с плавающей запятой в зависимости от того, над каким 4-байтовым блоком я сейчас работаю.

Например, если двоичный файл содержит N 288-байтовых блоков данных, каждый извлекаемый массив должен иметь по N членов. (За исключением последнего массива, который будет иметь 64N членов.)

Поскольку у меня есть двоичные данные в памяти, я просто хочу читать из буфера по одному 32-битному числу за раз и помещать полученное значение в соответствующий массив.

И наконец - могу ли я получить доступ к нескольким позициям массива одновременно, а-ля Matlab? (например, массив (3: 5) -> [1,2,1] для массива = [3,4,1,2,1])

Ответы [ 4 ]

3 голосов
/ 25 октября 2011

Во-первых, преимущество использования 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;
}
0 голосов
/ 24 октября 2011

Возможно, вы ищете библиотеки сериализации для C ++.Возможно, s11n может быть полезным.

0 голосов
/ 24 октября 2011

Этот вопрос показывает, как вы можете преобразовать данные из буфера в определенный тип.В общем, вы должны предпочесть использовать 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 -loop для вставки элементов в ваши массивы:

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 голосов
/ 24 октября 2011
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, то это вопрос абстракции. Вы можете абстрагировать содержимое вашего файла более удобным способом. Тогда вам не нужно работать с буферами, но вместо этого вы можете создать структуру, используя классы, а затем скрыть сведения о том, как она хранится в файле, перегружая, например, оператор <<. </p>

...