2015-03-12 4 views
1

Мне нужно прочитать файл в двоичном режиме и сохранить байты в виде шестнадцатеричных значений в любом контейнере STL (предпочтительно std :: list). Позже мне нужно записать их обратно в файл, также в двоичном режиме. Итак, я заявил,Чтение/запись шестнадцатеричных байтов из/в двоичный файл

typedef unsigned char BYTE; 

std::ifstream File("File_Name", std::ios::binary); 

std::list<BYTE> File_Bytes; 

Со всем поиском я понял несколько вещей. Чтение может быть выполнено с помощью std :: istream :: read() или std :: istreambuf_iterator (я мог бы быть очень не прав. Пожалуйста, поправьте меня.), А функция read() принимает только char * в качестве аргумента для сохраненных байтов в памяти и размер входного потока.

Как бы это сделать, когда мне нужно прочитать байты из файла в списке BYTE и снова написать аналогично записи из списка BYTE в файл, используя istream и ostream соответственно? Прошу прояснить это для меня. Благодарю.

Примечание. Это действительно для кодировщика/декодера Хаффмана, где мне нужно сжать и распаковать внутри программы и записать распакованные биты в качестве выходного файла. Это делается для проверки без потерь сжатия и правильности программы. Кроме того, может ли кто-нибудь сказать мне, как я буду писать закодированные бинарные биты в файл, а также какое расширение файла будет иметь закодированный файл Хаффмана? Большое спасибо.

+0

Почему вы предпочитаете std: list? – DaveyLaser

+0

Взгляните на это: [чтение файла в вектор с помощью итераторов [(http://stackoverflow.com/questions/3795326/using-insert-iterators-when-reading-from-file) –

+1

@laser_wizard std :: list has случайный доступ, который мне помог в других проблемах. Я знаю, что файлы записываются последовательно, но std :: list kinda стал моим основным преимуществом. Я знаю, что мы должны измениться в соответствии с тем, что подходит для программы, но это работало для меня в большинстве случаев, поэтому я использую его. Почему вы спрашиваете? Не подходит ли это в этом случае? – WDRKKS

ответ

3

Как осветляют комментарии, вы хотите загрузить байт двоичного файла в некоторый STL контейнер из char - точнее, uint8_t - и , чтобы сохранить такой контейнер обратно в двоичный файл.

Есть много способов сделать это, в том числе, как вы обнаружили, использование из std::basic_istream::read и std::basic_ostream::write, или std::istream_iterator и std::ostream_iterator.

Последний подход дает простейший код. Подход дает самый быстрый код, но проще для того, что, очевидно, будет простым прологовым и эпилоговым операциями вашей программы.

Вот соответствие пар функций шаблона, который будет соответственно:

Вернуть STL контейнер типа параметра Container, заполненный байт-последовательность входного файла.

Скопируйте элементы контейнера STL типа параметра Container в байтовую последовательность в выходной файл.

#include <fstream> 
#include <iterator> 
#include <algorithm> 
#include <stdexcept> 
#include <cstdint> 

template<class Container> 
Container binary_load(std::string const & bin_file_name) 
{ 
    std::ifstream in(bin_file_name,std::ios::binary); 
    if (!in) { 
     throw std::runtime_error("Could not open \"" + bin_file_name + 
      "\" for reading"); 
    } 
    std::noskipws(in); // PON 1 
    return Container(std::istream_iterator<std::uint8_t>(in), 
         std::istream_iterator<std::uint8_t>()); //PON 2 

} 

template<class Container> 
void binary_save(Container && data, std::string const & bin_file_name) 
{ 
    std::ofstream out(bin_file_name,std::ios::binary); 
    if (!out) { 
     throw std::runtime_error("Could not open \"" + bin_file_name + 
      "\" for writing"); 
    } 
    std::copy(data.begin(),data.end(), 
     std::ostream_iterator<std::uint8_t>(out,"")); // PON 3 
} 

Чтобы скомпилировать элементарный случай использования, добавьте это:

#include <vector> 
#include <string> 

using namespace std; 

int main(int argc, char *argv[]) 
{ 
    string infile = argv[1]; 
    string outfile = infile + ".saved"; 
    auto data(binary_load<vector<std::uint8_t>>(infile)); 
    binary_save(data,outfile); 
    return 0; 
} 

компилируется в C++ 11 или лучше. Полученная программа загружает файл, указанный вами в качестве первого аргумента командной строки , в std::vector<std::uint8_t>, а затем только сохраняет этот вектор в файл с таким же именем с дополнительным расширением .saved. Ваша программа, конечно же, загрузит один вектор и сохранит другой.

Points Of Примечание (PON):

  1. Это утверждение потребного для информирования потока in, что он должен черпать все байт, а не пропустить пробельные байт.

  2. Это утверждение непосредственно строит населенную Container из диапазона итератора [begin,end) , таким образом, что каждый STL контейнер может быть построен. begin итератора std::istream_iterator<char>(in)является запуск из-потока итератора для in и end итератора std::istream_iterator<char>() является истекшим потока итератора для каждого потока.

  3. Данное заявление копирует последовательность байтов в последовательные позиции std::ostream_iterator<char>, первоначально размещенных в начале out. Аргумент конструктора итератора сообщает, что пустая строка (т. Е. Ничего) должна отделять последовательные выходные байты.

Эти функциональные шаблоны имеют несколько более общо, чем строго требуется:

  • Тип Container, с которым вы призываете binary_load не должен быть контейнер uint8_t или даже прообразом тот же размер. Для этого требуется только тип контейнера, который может быть построен из диапазона итератора по последовательности uint8_t.

  • Аналогично Container тип, который вы вызываете binary_save потребность быть только один, элементы которого имеют тип E, который неявно конвертируемые в uint8_t, с той оговоркой, что усечение будет происходить если вы waywardly выбрали для сохранения E сек которые не были представлены в uint8_t.

Так положить их вместе, никакого вреда не будет сделано, например, если вы заменить vector<uint8_t> с vector<long> в примере программы.

Конечно, если вы по ошибке вызывается либо шаблон функции с типом контейнера, которые не удовлетворяют требованиям шаблона из Container, код будет не компилировать.

Продолжение комментариев OP в

Могу ли я использовать неподписанные символ вместо [из uint8_t]?

Да, uint8_t почти неизбежно определяются как unsigned char ваш компилятора (ов), а также любой 8-битный типа интегрального типа будет делать. uint8_t только наиболее четко говорит «байт».Если вы хотели для дальнейшего параметрирования функции шаблона относительно «байт» типа, вы могли бы сделать так, как:

... 
#include <type_traits> 

template<class Container, typename Byte = std::uint8_t> 
Container binary_load(std::string const & bin_file_name) { 

    static_assert(sizeof(Byte) == 1,"Size of `Byte` must be 1"); 

    // `std::uint8_t` becomes `Byte` 
    ... 
} 

template<class Container, typename Byte = std::uint8_t> 
void binary_save(Container && data, std::string const & bin_file_name) { 

    static_assert(sizeof(Byte) == 1,"Size of `Byte` must be 1"); 
    // `std::uint8_t` becomes `Byte` 
    ... 
} 

Что касается правого расширения файла для Хаффмана закодированы файлы, нет никакого стандартного де-факто. Выберите то, что вам нравится.

И если вам не требуется использовать MS VC10 (с патчей поддержки C++ 11), то для вашей версии консоли вам не понадобится . Bang up-to-date GCC toolchains are freely available for Windows и с поддержкой IDEs: CodeLite, Code::Blocks

+0

О, мой бог, Майк Кингхан, Спасибо вам большое! Когда вы спросили меня о моей проблеме, я не ожидал, что вы дадите мне объяснение с такой ясностью и такими деталями. Я понял все, что вы объяснили очень четко. Я скомпилировал код, и он работает как шарм. Еще раз спасибо. Престижность к вам! :-) – WDRKKS

+0

@WDRKKS Ответ обновлен для комментариев с момента удаления. Если этот ответ решает вашу проблему, вы можете [принять его] (http://stackoverflow.com/help/accepted-answer). Тогда он больше не остается без ответа; вы получаете некоторые точки репутации SO, и я тоже. –

+0

Да, я приму ответ. Большое вам спасибо за вашу помощь и дополнительную информацию. Сегодня я многому учу. Однако у меня есть еще одно последнее сомнение. Я использовал цикл for, чтобы распечатать все «байты» в «векторе» от начала до конца и сравнить его с файлом, открытым в шестнадцатеричном редакторе. Я заметил, что байты не находятся в том же порядке в окне вывода, что и в шестнадцатеричном редакторе. Почему это так? Не могли бы вы рассказать мне. Я использовал правильный метод для печати в правильном формате (шестнадцатеричный, двоичный, целочисленный и т. Д.). – WDRKKS

1

Я предлагаю использовать буферы фиксированного размера из uint8_t:

const unsigned int BUFFER_SIZE = 1024*1024; 
uint8_t buffer[BUFFER_SIZE]; 
// ... 
my_file.read((char *)buffer, BUFFER_SIZE); 

В вашей программе, вы бы прочитать буфер, обрабатывать его, а затем прочитать другой буфер от входа.

Массив - более эффективный контейнер, чем std::vector или std::list для ваших целей.

Кроме того, используйте uint8_t, потому что это стандартизованный тип.

+1

Что произойдет, если файл больше 1024^2? Правильное использование контейнеров может сделать код намного проще для записи и чтения. 'std :: vector :: resize()' может избежать дорогостоящего роста/копирования, связанного с большинством проблем с производительностью контейнера. – Freddy

1

Ваш вопрос затрагивает две разные темы.

  1. Как читать & написать двоичный файл
  2. Как манипулировать двоичные данные. (Надуть/выкачать)

File IO

Два популярных методов для чтения файлов является read() и getline(). Я использую read() при работе с бинарными файлами и getline() при чтении текстовых файлов в строке. Поскольку вы имеете дело с двоичными данными, я бы предложил использовать read().

// Open Binary file at the end 
std::ifstream input(filePath, std::ios::ate | std::ios::binary); 
assert(input.is_open()); 

// Calculate size 
size_t end = input.tellg(); 
input.seekg(0,std::ios::beg); 
size_t beg = input.tellg(); 
size_t len = end - beg; 
assert(len > 0); 

// Read in Binary data 
std::vector<char> binaryData(len); 
input.read(&(binaryData[0]),len); 

// Close 
input.close(); 

На этом этапе игры у вас есть все ваши двоичные данные, хранящиеся в векторе. Я знаю, что в вашем примере вы использовали вместо этого list, но при условии, что вы хотите иметь дело с непрерывным потоком байтов, vector кажется более строгим с тем, что вы делали.

Binary

Есть несколько способов, чтобы иметь дело с двоичными данными.Вы можете использовать надежные операторы смены << и >> в сочетании с некоторыми хорошими и & или или | логикой. Однако, если вы хотите получить более визуальное представление в своем коде, я бы предложил посмотреть на std::bitset.

Используя битрейт, вы можете легко загрузить содержимое своего vector в 8-битное представление в двоичном формате.

std::bitset<8> deflatedBinary(binaryData[0]); 
std::bitset<12> inflatedBinary; 

Первого bitset содержит 8-разрядное двоичное представление для вашего первого char и второго набора, inflatedBinary, имеет двенадцать бит все обнуляется. Отсюда вы можете получить доступ к своим элементам посредством индексации []. Вы можете узнать больше о std::bitsethere.

+0

Спасибо за информацию, Фредди. Хотя, у меня есть пара вопросов. Во-первых, вы создали вектор типа char. Это можно использовать для объявления даже для типа unsigned char (BYTE), правильно? Глупый вопрос, но убедившись. Во-вторых, двоичные данные в моей программе хранятся в std :: vector . И страница инструкций программы настаивает на использовании битовых операторов, как вы также предложили. Могу ли я использовать этот вектор вместе с этими операторами? Прошу прояснить это для меня. Благодарю. – WDRKKS

+0

Я использовал 'char', но мог бы так же легко использовать' unsigned char'. Честно говоря, у Томаса Мэтьюза есть лучший подход к использованию 'uint_8', так как его размер гарантированно составляет 1 байт. Мой код делает опасное предположение, что char/unsigned char всегда 1 байт, что для некоторых машин не так. К вашему второму вопросу, 'std :: vector' является просто контейнером, вы не хотите выполнять на нем битовые операторы. Похоже, ваш профессор хочет, чтобы вы использовали true/false 'bool' в контейнере, как если бы он был двоичным. – Freddy

+0

Спасибо, что сказал мне это. Я прочитал на 'std :: bitset' и заметил, что вы использовали битовый набор для того, чтобы те же байты считывали из файла в виде двоичных данных. Я должен был уточнить свой вопрос. По двоичным данным я имел в виду кодированные по Хаффману значения, относящиеся к каждому байту, где длина каждого значения является переменной, поскольку она основана на частоте байта. Я вижу, что длина 'std :: bitset' постоянна, а пустые биты заполняются нулем. Это изменит кодированные значения. Есть ли обходной путь? Если нет, 'std :: vector ' работает очень хорошо для 'std :: strings'. Это может хорошо работать и с байтами. – WDRKKS

 Смежные вопросы

  • Нет связанных вопросов^_^