【问题标题】:Writing/Reading a std::map to a binary file require operator将 std::map 写入/读取到二进制文件需要运算符
【发布时间】:2017-05-14 08:00:23
【问题描述】:

我想将 std::map 写入文件并将其读回。我正在寻找一种相当简单且极简的方式来做到这一点,而无需提升。我发现这里的矢量是可行的Reading and writing a std::vector into a file correctly with iterators

我发现了这个问题,因为它与我想做的事情有关,但我正在寻找二进制替代方案。

reading a file of key-value pairs in to a std::map

【问题讨论】:

  • 对此没有神奇的解决方案。您的映射对象(和键)需要可序列化,以便您可以将它们写入某种记录或子记录。这里没有开箱即用的解决方案,因为答案取决于您的数据。

标签: c++ file c++11 iterator


【解决方案1】:

对于不涉及动态内存(实际上是指针)的类型

template<size_t N>
struct Serial
{
    char bin[N];
    friend ostream& operator<<(ostream& os, const Serial& s)
    {
        for(auto c : bin)
            os << c;
        return os;
    }
    friend istream& operator>>(istream& is, Serial& s)
    {
        for(auto& c : bin)
            is >> c;
        return is;
    }
};

struct Key
{
    static constexpr size_t size = sizeof(Key);
    Key(const Serial<size>& s) { memcpy(this, s.bin, size); }
    Serial<size>& serialize() { return *reinterpret_cast<Serial<size>*>(this); }
};

struct Value
{
    static constexpr size_t size = sizeof(Value);
    Key(const Serial<size>& s) { memcpy(this, s.bin, size); }
    Serial<size>& serialize() { return *reinterpret_cast<Serial<size>*>(this); }
};

void write(ostream& os, const std::map<Key, Value>& m)
{
    for(const auto& p : m)
        os << p.first.serialize() << p.second.serialize();
}

void read(istream& is, std::map<Key, Value>& m)
{
    Serial<Key::size> k;
    Serial<Value::size> v;
    while(is >> k >> v)
        m[k] = v;
}

对于涉及动态内存(指针)的类型,解决方案将完全取决于它们的工作方式,无法提供神奇的解决方案。

你考虑过JSON吗?

【讨论】:

    【解决方案2】:

    欢迎来到序列化的混乱、混乱、不一致的世界。希望你喜欢这个旅程!

    这是一个古老的问题:如何将适度复杂的数据结构写入某种文本或二进制格式,然后能够稍后将其读回。有几种不同的方法可以做到这一点。但是,您说要序列化为二进制格式,所以我建议使用MessagePack

    有一个用于处理 MessagePack 格式的 C++11 库,名为 msgpack11,它也相当轻量级,似乎符合您的要求。这是一个例子:

    std::map<A, B> my_map;
    // To save my_map:
    msgpack11::MsgPack msgpack{my_map};
    std::string binary_data = msgpack.dump();
    // Now you can save binary_data to a file.
    
    // To get the map back:
    string error_string;
    auto msgpack = msgpack11::MsgPack::parse(binary_data, error_string);
    std::map<A, B> my_map;
    // Now you need to manually read back the data.
    

    【讨论】:

      【解决方案3】:

      对于二进制写入,你应该使用 ostream 的 write 方法

      ostream& ostream::write (const char* s, streamsize n);
      

      在此处查看文档:http://en.cppreference.com/w/cpp/io/basic_ostream

      你不能直接将map写入文件,你应该写它的represntation,由你开发。您需要单独编写每个键/值对,或者将它们缓冲为一个数据块并将其写入文件。不过,这实际上并不比 for 循环复杂多少。如果 map 包含非平凡构造和销毁的类,您应该实现一个允许序列化类的二进制数据的方法。

      【讨论】:

      • 如何,确切地解决了问题中的问题?
      • 它没有。我想写一张地图,直接用二进制,而不是char数组,我也想读回来。
      • @jokoon 除了 C++ 中的 char 之外没有其他数据来操作原始二进制数据。 Char 相当于字节,是最小可寻址存储。
      【解决方案4】:

      二进制实现必然是不可移植的(对于生成的文件)。如果这不是问题,那么考虑定义一个使用内存映射文件的自定义分配器。然后,您将使用该分配器作为模板参数之一声明您的 std:map。您可以直接使用该地图,也可以使用范围插入将现有地图保存到文件中。如果键或值需要分配器(例如字符串),您必须在模板声明中使用内存映射分配器声明这些类型的版本,并定义从键/值类型到新类型的赋值运算符。 您可以通过搜索“内存映射文件 stl 分配器”找到一些分配器实现和进一步讨论。另见:Memory mapped file storage in stl vector

      【讨论】:

        【解决方案5】:
        void BinSerialize(ostream &out, int32_t x);
        void BinSerialize(ostream &out, int16_t x);
        void BinSerialize(ostream &out, int8_t x);
        
        void BinSerialize(ostream &out, const string &s)
        {
            BinSerialize(out, (int32_t)s.size());
            out.write(size.c_str(), s.size()));
        }
        
        temmplate<class KeyT, ValueT>
        void BinSerialize(ostream &out, const std::map<KeyT, ValueT> &m)
        {
            BinSerialize(out, (int32_t)m.size());
            for (auto& item : m)
            {
                BinSerialize(out, item.first);
                BinSerialize(out, item.second);
            }
        }
        
        void BinDeserialize(istream &input, int32& x);
        void BinDeserialize(istream &input, int16& x);
        void BinDeserialize(istream &input, int8& x);
        
        void BinDeserialize(istream &input, string &s)
        {
            int32_t size;
            BinDerialize(out, size);
            s.resize(size);
            out.read(size.c_str(), size);
        }
        
        temmplate<class KeyT, class ValueT>
        void BinDeserialize(istream &input, std::map<KeyT, ValueT> &m)
        {
            int32_t size;
            m.clear();
            BinDeserialize(out, size);
            for (int32_t i=0; i<size; ++i)
            {
                std::pair<KeyT, ValueT> item;
                BinDeserialize(out, item.first);
                BinDeserialize(out, item.second);
                m.insert(item);
            }
        }
        

        这写得很快。可以使用模板对其进行改进,以涵盖所有基本类型和所有 STL 容器。

        另外,最好记住字节序。

        在这种情况下最好避免使用重载运算符。但是,如果您这样做,最好定义将包装 STL 流并拥有自己的一组重载 &gt;&gt; &lt;&lt; 运算符的类。看看Qt QDataStream

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-29
          • 2012-06-08
          • 1970-01-01
          • 2021-10-18
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多