【问题标题】:How to work with a buffer and binary files如何使用缓冲区和二进制文件
【发布时间】:2021-01-30 11:19:37
【问题描述】:

我正在尝试将每个字符串的向量写入二进制文件,然后将其读回。我不能再进一步了,因为我的缓冲区有问题。我试着把它当作一个 char * 但我不能。

代码:

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

using namespace std;

void write_bin() {
    vector<string> vec = {"Text1", "Text2", "Text3", "Text4"};
    ofstream bin_out("binary.bin", ios::binary | ios::out | ios::trunc);

    if (!bin_out.is_open()) {

        // Set n size of vec first
        bin_out.write((char *) vec.size(), sizeof(int));

        for (string s : vec) {
            size_t size = s.size();
            bin_out.write((char *)size, sizeof(size));
            bin_out.write(&s[0], size);
        }
        bin_out.close();
    }
}

void read_bin() {
    ifstream bin_in("binary.bin", ios::binary | ios::in);
    string s;
    char *buffer;
    bin_in.read(buffer, sizeof(int));
    for (int i = 0; i < atoi(buffer); ++i) {
        bin_in.read(buffer, sizeof(int));
        size_t size = atoi(buffer);
        bin_in.read(&buffer[0], size);
        s = buffer;
        cout << s << endl;
    }
}

int main() {
    write_bin();
    read_bin();
}

这样做的正确方法是什么?

【问题讨论】:

  • 生活准则:如果您在 C++ 代码中进行 C 转换以消除警告/错误,那么您很可能已经做错了。
  • 恕我直言,这种方法是错误的。您应该使用经典的反序列化技术并覆盖插入器和提取器运算符。无需存储大小。只需将向量逐行写入文本文件,然后使用std::getline 读回所有内容。这很容易,也更容易理解。

标签: c++ vector binary buffer


【解决方案1】:

您正在强制转换以使 size 的值成为指针,而实际上您想要指向存储在 size 中的内容的指针。

所以

size_t size = s.size();
bin_out.write((char *)size, sizeof(size));

应该是

size_t size = s.size();
bin_out.write(reinterpret_cast<const char*>(&size), sizeof(size));

(适用于您犯相同错误的任何地方)

另外,这部分搞砸了:

    char *buffer;
    bin_in.read(buffer, sizeof(int));
    for (int i = 0; i < atoi(buffer); ++i) {

atoi 尝试将字节字符串(例如“10”)转换为整数。除非buffer 中的第一个char('0', '9'] 范围内(其中'0' 通常是char,值为48),否则它将返回0。你需要一些东西:

    size_t buffer_size;
    bin_in.read(reinterpret_cast<char*>(&buffer_size), sizeof(buffer_size));
    for (size_t i = 0; i < buffer_size; ++i) {

...但请阅读以下内容,了解如何正确地将二进制格式的大小写入/读取文件。


这样做的正确方法是什么?

除非您计划存储包含 \0\n 的字符串,否则使用 4 或 8 个字节(size_t 的常见大小)来存储大小只是一种浪费 - 它会使您的代码不可移植因为你没有考虑字节序。

我建议使用\0\n 编写字符串以标记它们的结尾,并使用getline 将它们读回。

如果您想在存储非常大的向量时节省几个字节,请确保将其存储为可在所有平台上读取的格式。您目前以不可移植的格式存储它。

  • size_t 并非在所有平台上都具有相同的 sizeof(size_t)
  • 字节序在所有平台上都不相同。

以与平台无关的方式存储大小的尝试可能是使用函数在本机格式和您存储在文件中的内容之间来回转换。您可能有可用的 htons/ntohshtonl/ntohl 函数 - 或者您可以编写自己的函数。

C++20 中的朴素实现:

using size_type = std::uint16_t; // use a fixed width type to store sizes

size_type convert_size(std::size_t value) {
    if(value > std::numeric_limits<size_type>::max())
        throw std::length_error("value to big: " + std::to_string(value));

    size_type retval = value;

    // decide on a format that can be read on all platforms.

    static_assert(std::endian::native == std::endian::little ||
                  std::endian::native == std::endian::big,
                  "Mixed endianess not supported");

    // Here I've opted to go for big endian:
    if constexpr (std::endian::native != std::endian::big) {
        // swap the byte order if your platform uses little endianess
        std::reverse(reinterpret_cast<char*>(&retval),
                     reinterpret_cast<char*>(&retval) + sizeof(retval));
    }

    return retval;
}

然后是这样的程序:

int main() {
    size_t foo = 100;
 
    auto size_to_write_to_file = convert_size(foo);
    std::cout << size_to_write_to_file << '\n';

    size_t size_read_from_file = size_to_write_to_file;

    std::cout << convert_size(size_read_from_file) << '\n';
}

会打印25600100100100,具体取决于您运行它的平台 - 但它总是以100 作为您回读时的大小.

【讨论】:

    【解决方案2】:

    在你问题的最后一行,你问的是:

    这样做的正确方法是什么?

    答案是:使用序列化。

    不要尝试将复杂数据存储为二进制数据。由于操作系统和/或文件系统的不同属性,这通常不起作用。最便携的方法是使用文本文件或其他格式的结构,如 XML 或 JSON 或其他。您也可以使用 Google 的 protobuf 等库

    但是对于这个简单的示例,我们将自己进行序列化。我们创建一个数据结构并简单地覆盖提取器和插入器操作符。这样,我们就可以将所有 IOStream 工具用于我们自己的类型。没关系,如果我们将数据插入std::coutstd::ofstream 或任何地方。所以,这是首选方法。

    请看下面非常简单的示例代码:

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <string>
    #include <algorithm>
    #include <iterator>
    
    struct MyData {
        // Our vectro with strings
        std::vector<std::string> text{};
    
        // Override extractor operator
        friend std::istream& operator >> (std::istream& is, MyData& md) {
            for (std::string line{}; std::getline(is, line); md.text.emplace_back(std::move(line)));
            return is;
        }
        // overwrite inserter operator
        friend std::ostream& operator << (std::ostream& os, const MyData& md) {
            std::copy(md.text.begin(), md.text.end(), std::ostream_iterator<std::string>(os, "\n"));
            return os;
        }
    };
    
    const std::string fileName{"test.txt"};
    
    int main() {
    
        // Our test data
        MyData myData1{ {"text1", "text2", "text3"} };;
    
        // Open file for writing and check, if it could be opened.
        if (std::ofstream ofs{ fileName }; ofs) {
    
            // Write all data from test vector to file
            ofs << myData1;
        }
        else std::cerr << "\n\nError. Could not open file '" << fileName << "' for writing\n\n";
    
        // Open file for reading and check, if it could be opened.
        if (std::ifstream ifs{ fileName }; ifs) {
    
            // We will read from the file to this new variable
            MyData myData2{};
    
            // Read all data from file
            ifs >> myData2;
    
            // Show result on screen
            std::cout << myData2;
        }
        else std::cerr << "\n\nError. Could not open file '" << fileName << "' for reading\n\n";
    
        return 0;
    }
    

    您需要在启用 C++17 的情况下进行编译,因为我使用带有初始化程序的 if 语句。

    【讨论】:

    • 看起来不错,但它不是避开了主题“如何使用 [...] 二进制文件”?
    • 是的,它确实避免了这个话题。我的信息是:不要使用 POD 以外的二进制文件。所以,不适用于std::vector 和其他类。
    猜你喜欢
    • 2012-10-03
    • 1970-01-01
    • 2011-07-05
    • 2013-05-20
    • 2014-11-21
    • 1970-01-01
    • 1970-01-01
    • 2016-09-16
    • 2010-09-06
    相关资源
    最近更新 更多