【问题标题】:efficent way to save objects into binary files将对象保存到二进制文件中的有效方法
【发布时间】:2011-07-01 15:05:48
【问题描述】:

我有一个基本上由向量矩阵组成的类:vector< MyFeatVector<T> > m_vCells,其中外部向量表示矩阵。这个矩阵中的每个元素都是一个vector(我扩展了stl vector 类并将其命名为MyFeatVector<T>)。

我正在尝试编写一种有效的方法来将此类的对象存储在二进制文件中。 到目前为止,我需要三个嵌套循环:

foutput.write( reinterpret_cast<char*>( &(this->at(dy,dx,dz)) ), sizeof(T) );

其中this->at(dy,dx,dz) 在位置[dy,dx] 处检索向量的dz 元素。

是否有可能在不使用循环的情况下存储m_vCells 私有成员?我尝试了类似的方法:foutput.write(reinterpret_cast<char*>(&(this->m_vCells[0])), (this->m_vCells.size())*sizeof(CFeatureVector<T>)); 似乎无法正常工作。我们可以假设这个矩阵中的所有向量都具有相同的大小,尽管也欢迎使用更通用的解决方案:-)

此外,按照我的嵌套循环实现,将此类的对象存储在二进制文件中似乎比将相同的对象存储在纯文本文件中需要更多的物理空间。这有点奇怪。

我试图遵循http://forum.allaboutcircuits.com/showthread.php?t=16465 下的建议,但无法找到合适的解决方案。

谢谢!

下面是我的serializationunserialization 方法的简化示例。

template < typename T >
bool MyFeatMatrix<T>::writeBinary( const string & ofile ){

    ofstream foutput(ofile.c_str(), ios::out|ios::binary);
    foutput.write(reinterpret_cast<char*>(&this->m_nHeight), sizeof(int));
    foutput.write(reinterpret_cast<char*>(&this->m_nWidth), sizeof(int));
    foutput.write(reinterpret_cast<char*>(&this->m_nDepth), sizeof(int));

    //foutput.write(reinterpret_cast<char*>(&(this->m_vCells[0])), nSze*sizeof(CFeatureVector<T>));
    for(register int dy=0; dy < this->m_nHeight; dy++){
       for(register int dx=0; dx < this->m_nWidth; dx++){
          for(register int dz=0; dz < this->m_nDepth; dz++){
              foutput.write( reinterpret_cast<char*>( &(this->at(dy,dx,dz)) ), sizeof(T) );
          }
       }
    }

    foutput.close();
    return true;
}

template < typename T >
bool MyFeatMatrix<T>::readBinary( const string & ifile ){

    ifstream finput(ifile.c_str(), ios::in|ios::binary);

    int nHeight, nWidth, nDepth;
    finput.read(reinterpret_cast<char*>(&nHeight), sizeof(int));
    finput.read(reinterpret_cast<char*>(&nWidth), sizeof(int));
    finput.read(reinterpret_cast<char*>(&nDepth), sizeof(int));

    this->resize(nHeight, nWidth, nDepth);

    for(register int dy=0; dy < this->m_nHeight; dy++){
        for(register int dx=0; dx < this->m_nWidth; dx++){
            for(register int dz=0; dz < this->m_nDepth; dz++){
                finput.read( reinterpret_cast<char*>( &(this->at(dy,dx,dz)) ), sizeof(T) );
            }
        }
    }
    finput.close();
    return true;
}

【问题讨论】:

  • 为什么不编写自己的矩阵类,将数据内部保存在一维数组中(使用std::array),您可以直接写出来?
  • 子类化 STL 容器可能是个坏主意。它们不是为子类化而设计的。请注意,例如,std::vector 没有虚拟析构函数。
  • 你可能对Boost.Serialization感兴趣。
  • @Kerrek SB,很好的建议,但我需要像我一样考虑私人成员。 @Martinho Fernandes,我不熟悉子类化,由于某些原因我不能使用 boost。你有其他好的网络指针吗?
  • @Peter:我不明白你指的是哪些私人成员。除了矩阵数据之外,您还需要存储任何数据吗?在任何情况下,只需将矩阵数据存储在一维数组中并以适当的步幅访问它。

标签: c++ file file-io stl


【解决方案1】:

最有效的方法是将对象存储到数组(或连续空间)中,然后将缓冲区爆破到文件中。一个优点是磁盘盘片不会浪费时间加速,而且写入可以连续执行,而不是在随机位置。

如果这是您的性能瓶颈,您可能需要考虑使用多个线程,一个额外的线程来处理输出。将对象转储到缓冲区中,设置标志,然后写入线程将处理输出,释放主任务以执行更重要的任务。

编辑 1:序列化示例
以下代码未经编译,仅用于说明目的。

#include <fstream>
#include <algorithm>

using std::ofstream;
using std::fill;

class binary_stream_interface
{
    virtual void    load_from_buffer(const unsigned char *& buf_ptr) = 0;
    virtual size_t  size_on_stream(void) const = 0;
    virtual void    store_to_buffer(unsigned char *& buf_ptr) const = 0;
};

struct Pet
    : public binary_stream_interface,
    max_name_length(32)
{
    std::string     name;
    unsigned int    age;
    const unsigned int  max_name_length;

    void    load_from_buffer(const unsigned char *& buf_ptr)
        {
            age = *((unsigned int *) buf_ptr);
            buf_ptr += sizeof(unsigned int);
            name = std::string((char *) buf_ptr);
            buf_ptr += max_name_length;
            return;
        }
    size_t  size_on_stream(void) const
    {
        return sizeof(unsigned int) + max_name_length;
    }
    void    store_to_buffer(unsigned char *& buf_ptr) const
    {
        *((unsigned int *) buf_ptr) = age;
        buf_ptr += sizeof(unsigned int);
        std::fill(buf_ptr, 0, max_name_length);
        strncpy((char *) buf_ptr, name.c_str(), max_name_length);
        buf_ptr += max_name_length;
        return;
    }
};


int main(void)
{
    Pet dog;
    dog.name = "Fido";
    dog.age = 5;
    ofstream    data_file("pet_data.bin", std::ios::binary);

    // Determine size of buffer
    size_t  buffer_size = dog.size_on_stream();

    // Allocate the buffer
    unsigned char * buffer = new unsigned char [buffer_size];
    unsigned char * buf_ptr = buffer;

    // Write / store the object into the buffer.
    dog.store_to_buffer(buf_ptr);

    // Write the buffer to the file / stream.
    data_file.write((char *) buffer, buffer_size);

    data_file.close();
    delete [] buffer;
    return 0;
}

编辑 2:具有字符串向量的类

class Many_Strings
    : public binary_stream_interface
{
    enum {MAX_STRING_SIZE = 32};

    size_t    size_on_stream(void) const
    {
        return m_string_container.size() * MAX_STRING_SIZE  // Total size of strings.
               + sizeof(size_t); // with room for the quantity variable.
    }

    void      store_to_buffer(unsigned char *& buf_ptr) const
    {
        // Treat the vector<string> as a variable length field.
        // Store the quantity of strings into the buffer,
        //     followed by the content.
        size_t string_quantity = m_string_container.size();
        *((size_t *) buf_ptr) = string_quantity;
        buf_ptr += sizeof(size_t);

        for (size_t i = 0; i < string_quantity; ++i)
        {
            // Each string is a fixed length field.
            // Pad with '\0' first, then copy the data.
            std::fill((char *)buf_ptr, 0, MAX_STRING_SIZE);
            strncpy(buf_ptr, m_string_container[i].c_str(), MAX_STRING_SIZE);
            buf_ptr += MAX_STRING_SIZE;
        }
    }
    void load_from_buffer(const unsigned char *& buf_ptr)
    {
        // The actual coding is left as an exercise for the reader.
        // Psuedo code:
        //     Clear / empty the string container.
        //     load the quantity variable.
        //     increment the buffer variable by the size of the quantity variable.
        //     for each new string (up to the quantity just read)
        //        load a temporary string from the buffer via buffer pointer.
        //        push the temporary string into the vector
        //        increment the buffer pointer by the MAX_STRING_SIZE.
        //      end-for
     }
     std::vector<std::string> m_string_container;
};

【讨论】:

  • 所以我可以创建一个临时 char 数组,我首先转储整个内容,即所有向量的所有元素。然后,通过调用write 函数将数组存储到文件中。对吗?
  • @Peter:是的,你是对的。我使用unsigned char 作为我的数组。我还创建了一个用于存储到缓冲区、从缓冲区加载并返回缓冲区大小的接口。这允许将对象轻松连接到一个大缓冲区中。 size 函数有助于确定所需的缓冲区大小。
  • 谢谢!我当时理解正确。您可能有一些示例代码吗?
  • 我添加了一个序列化接口的例子。该接口允许嵌套对象和多个对象。首先使用size_on_stream 确定缓冲区的大小。其次,使用结果值分配缓冲区。第三,将对象存入缓冲区。最后,将缓冲区写入流。
  • 感谢您的插图。至于size_on_stream() 部分,你使用max_name_length 变量,手动设置为32,我猜这是你结构的大小,对吧?所以,我们应该考虑一下,而不是硬编码这个变量。喜欢 sizeof(myClass)?
【解决方案2】:

我建议您阅读C++ FAQ on Serialization,您可以选择最适合您的内容

当您使用结构和类时,您必须注意两件事

  • 类内的指针
  • 填充字节

这两者都可能在您的输出中产生一些臭名昭著的结果。 IMO,对象必须实现序列化和反序列化对象。对象可以很好地了解结构、指针数据等。因此它可以决定哪种格式可以有效地实现。

无论如何,您都必须进行迭代或将其包装在某个地方。完成序列化和反序列化功能的实现后(您可以使用运算符或函数编写)。尤其是当您使用stream objects, overloading << and >> operators 时会很容易传递对象。

关于使用向量底层指针的问题,如果它是单个向量,它可能会起作用。但这不是一个好主意。


根据问题更新更新。

在覆盖 STL 成员之前,您应该注意几件事情。它们并不是继承的好选择,因为它没有任何虚拟析构函数。如果您使用基本数据类型和类似 POD 的结构,则不会产生太大问题。但是如果你使用真正面向对象的方式,你可能会面临一些不愉快的行为。

关于你的代码

  • 为什么要将其类型转换为 char*?
  • 序列化对象的方式由您选择。 IMO 你所做的是以序列化的名义进行的基本文件写入操作。
  • 序列化到对象。即模板类中的参数“T”。如果您使用的是 POD,或者基本类型不需要特殊同步。否则,您必须仔细选择编写对象的方式。
  • 选择文本格式或二进制格式是您的选择。与二进制格式相比,文本格式总是有成本,但易于操作。

例如下面的代码是简单的读写操作(文本格式)。

fstream fr("test.txt", ios_base::out | ios_base::binary );
for( int i =0;i <_countof(arr);i++)
    fr << arr[i] << ' ';

fr.close();

fstream fw("test.txt", ios_base::in| ios_base::binary);

int j = 0;
while( fw.eof() || j < _countof(arrout))
{
    fw >> arrout[j++];
}

【讨论】:

  • 我已经实现了我自己的serializationunserialization 方法。我已经编辑了我的原始帖子并添加了代码。你会建议这样做吗?
  • 谢谢。我一直认为在处理binary 文件时,需要使用read()write() 函数而不是&gt;&gt;&lt;&lt; 运算符。这样做是否有效?此外,对于 STL 成员,这意味着只要我选择 composite 而不是 inheritance 就可以了,对吧?我没有覆盖这些类,而是在内部使用它们现有的方法。在继承中使用虚拟析构函数的情况下,如果我在析构函数中使用STL成员的clear()方法,应该没问题吧?
【解决方案3】:

在我看来,生成包含向量的二进制文件的最直接根是内存映射文件并将其放置在映射区域中。正如sarat 所指出的,您需要担心指针在类中的使用方式。但是,boost-interprocess library 有一个 tutorial 说明如何使用包括 memory mapped files 在内的共享内存区域来执行此操作。

【讨论】:

    【解决方案4】:

    首先,你看过Boost.multi_array吗?采用现成的东西而不是重新发明轮子总是好的。

    也就是说,我不确定这是否有帮助,但这是我将如何实现基本数据结构的方法,并且序列化相当容易:

    #include <array>
    
    template <typename T, size_t DIM1, size_t DIM2, size_t DIM3>
    class ThreeDArray
    {
      typedef std::array<T, DIM1 * DIM2 * DIM3> array_t;
      array_t m_data;
    
    public:
    
      inline size_t size() const { return data.size(); }
      inline size_t byte_size() const  { return sizeof(T) * data.size(); }
    
      inline T & operator()(size_t i, size_t j, size_t k)
      {
         return m_data[i + j * DIM1 + k * DIM1 * DIM2];
      }
    
      inline const T & operator()(size_t i, size_t j, size_t k) const
      {
         return m_data[i + j * DIM1 + k * DIM1 * DIM2];
      }
    
      inline const T * data() const { return m_data.data(); }
    };
    

    可以直接序列化数据缓冲区:

    ThreeDArray<int, 4, 6 11> arr;
    /* ... */
    std::ofstream outfile("file.bin");
    outfile.write(reinterpret_cast<char*>(arr.data()), arr.byte_size());
    

    【讨论】:

    • 非常感谢!顺便说一句,您将如何重载operator() 以检索位置(i,j) 的子数组?例如std::array&lt;T&gt; &amp; operator()(size_t i, size_t j)?是否也可以从原始数组中完全删除位置(i,j)的子数组?
    • @Peter:什么是“子数组”?有很多方法可以选择 3 维空间的 2 维子集,你想要哪一种?一般的答案是,在 (i,j,k) 上使用现有运算符并适当地修复其中一个值...
    • 子数组可以理解为R^DIM3空间中的一个向量。这个想法是,给定一对对应于 (rows,cols) 的坐标 (i,j),我想检索整个深度 (DIM3) 上的所有值。
    • @Peter:我明白了。这取决于你想用它做什么。您可以制作该范围的副本,但这会很昂贵。仅供阅读,您可能应该为此目的设计一些自定义迭代器。
    • 我基本上需要执行两个操作。第一个是删除位置(i,j)处的R^DIM3向量。因此,新的ThreeDArray 大小将为(DIM1 * DIM2 - 1) * DIM3。第二个操作是在(i,j) 位置分配/检索 R^DIM3 向量。您介意举个例子还是指出一个好的网络教程?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-18
    • 2011-11-10
    • 1970-01-01
    • 1970-01-01
    • 2015-10-03
    • 1970-01-01
    相关资源
    最近更新 更多