Reading/Writing 十六进制字节 from/to 二进制文件

Reading/Writing Hex Bytes from/to a Binary File

我需要以二进制模式读取文件并将字节作为十六进制值存储在任何 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 列表,然后再次类似地分别使用 istream 和 ostream 从 BYTE 列表写入文件,我将如何执行此操作?请为我澄清这一点。谢谢

注意:这实际上是针对 Huffman encoder/decoder,我需要在程序内部压缩和解压缩并将解压缩的位写入输出文件。这是为了验证压缩的无损性和程序的正确性。此外,谁能告诉我如何将编码的二进制位写入文件以及编码的霍夫曼文件的文件扩展名是什么?非常感谢。

我建议使用 uint8_t 的固定大小缓冲区:

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

在您的程序中,您将读取一个缓冲区,对其进行处理,然后从输入中读取另一个缓冲区。

对于您的用途而言,数组是比 std::vectorstd::list 更高效的容器。

此外,使用 uint8_t 因为它是标准化类型。

您的问题涉及两个截然不同的主题。

  1. 如何读写二进制文件
  2. 如何操作二进制数据。 (inflate/deflate)

文件 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 似乎更符合您的操作。

二进制

有几种方法可以处理二进制数据。您可以使用可靠的移位运算符 <<>> 以及一些好的 & 和/或 | 逻辑。但是,如果您想在代码中获得更直观的表示,我建议您查看 std::bitset.

使用位集,您可以轻松地将 vector 的内容加载到二进制的 8 位表示中。

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

第一个 bitset 保存第一个 char 的 8 位二进制表示,第二个集合 inflatedBinary 的 12 位全部归零。从这里您可以通过索引 [] 访问它们的元素。您可以阅读更多关于 std::bitset here.

如评论所述,您想将二进制文件的字节加载到 char 的一些 STL 容器 - 或者更准确地说,uint8_t - 和 将这样的容器保存回二进制文件。

有很多方法可以做到这一点,包括如您所见,使用 std::basic_istream::read std::basic_ostream::write, 或 std::istream_iteratorstd::ostream_iterator.

后一种方法产生最简单的代码。 fread/fwrite 方法 产生最快的代码,但显然更简单更好 将仅仅是您程序的序幕和结尾操作。

这是一对匹配的模板函数,它们将分别是:

Return 参数类型 Container 的 STL 容器,填充有 输入文件的字节序列。

将参数类型 Container 的 STL 容器的元素复制到 输出文件中的字节序列。

#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。当然,您的程序将加载一个向量 并保存一个不同的。

注意事项 (PON):

  1. 需要此语句来通知流 in 它应该提取 所有 字节,而不是跳过空白字节。

  2. 此语句直接从 [begin,end) 构造填充的 Container 迭代器范围,以可以构造每个 STL 容器的方式。 begin 迭代器 std::istream_iterator<char>(in)start-of-stream in 的迭代器和 end 的迭代器 std::istream_iterator<char>() 是每个流的 end-of-stream 迭代器。

  3. 这条语句将字节序列复制到一个 std::ostream_iterator<char> 最初位于 out 的开头。 迭代器构造函数的 "" 参数通知它 空字符串(即没有任何内容)应分隔连续的输出字节。

这些函数模板比​​你严格意义上的更通用 要求:

  • 调用 binary_loadContainer 类型不需要 uint8_t 甚至相同大小的容器。它需要 只能是一个容器类型,可以从一个迭代器范围构造一个 uint8_t.

  • 的序列
  • 同样,您调用 binary_saveContainer 类型需要 只能是其元素属于隐式类型 E 的元素 可转换为 uint8_t,但要注意会发生截断 如果您任性地选择保存在 uint8_t 中无法表示的任何 E

所以把这些放在一起,没有什么坏处,例如,如果你 在示例程序中将 vector<uint8_t> 替换为 vector<long>

当然,如果您错误地使用 不满足模板要求的容器类型 Container,代码无法编译。

继续 OP 的评论

Can I use unsigned char instead [of uint8_t]?

是的,uint8_t 几乎不可避免地被您定义为 unsigned char 编译器和任何 8 位整数类型都可以。 uint8_t刚刚 最清楚地说 "byte"。如果你愿意 进一步参数化关于 "byte" 的模板函数 输入,你可以这样做:

...
#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 支持) 对于您的控制台版本,没有必要。 升级最新的 GCC 工具链 Windows和 支持的 IDE:CodeLite,Code::Blocks