在没有序列化库的情况下,在c ++中从一个文件读取和写入数据的最简单方法是什么?

时间:2016-04-12 14:31:20

标签: c++ fstream

我正在编写一个程序来定期存储和读取下面表格中的结构。

struct Node {
    int leftChild = 0;
    int rightChild = 0;
    std::string value;
    int count = 1;
    int balanceFactor = 0;
};

我如何读取和写入文件的节点?我想使用带有seekg和seekp的fstream类来手动进行序列化,但是我不确定它是如何工作的,基于文档并且正在努力寻找合适的例子。

[edit]指定我不想使用序列化库。

6 个答案:

答案 0 :(得分:5)

此问题称为serialization。使用序列化库,例如Google的Protocol BuffersFlatbuffers

答案 1 :(得分:3)

要序列化对象,您需要坚持对象将其成员写入流并从流中读取成员的概念。此外,成员对象应将自己写入流(以及读取)。

我使用三个成员函数和一个缓冲区实现了一个方案:

void load_from_buffer(uint8_t * & buffer_pointer);  
void store_to_buffer(uint8_t * & buffer_pointer) const;  
unsigned int size_on_stream() const;  

首先调用size_on_stream以确定对象的缓冲区大小(或缓冲区中占用的空间大小)。

load_from_buffer函数使用给定指针从缓冲区加载对象的成员。该函数还适当地递增指针。

store_to_buffer函数使用给定指针将对象的成员存储到缓冲区。该函数还适当地递增指针。

这可以通过使用模板和模板特化应用于POD类型。

这些函数还允许您将输出打包到缓冲区中,并从打包格式加载。

缓冲区I / O的原因是您可以使用更高效的流方法,例如writeread

编辑1:将节点写入流
编写或序列化节点(如链接列表或树节点)的问题是指针不会转换为文件。无法保证操作系统会将您的程序放在同一个内存位置,或者每次都为您提供相同的内存区域。

您有两种选择:1)仅存储数据。 2)将指针转换为文件偏移量。选项2)非常复杂,因为它可能需要重新定位文件指针,因为文件偏移可能不会提前知道。

另外,要注意像字符串这样的可变长度记录。您不能直接将字符串对象写入文件。除非使用固定的字符串宽度,否则字符串大小将发生变化。您将需要在字符串前加上字符串长度(首选)或使用某种终止字符,例如'\ 0'。首先是字符串长度,因为您不必搜索字符串的结尾;您可以使用块读取来读取文本。

答案 2 :(得分:1)

如果用char缓冲区替换std :: string,则可以使用fwrite和fread作为固定大小的信息块在磁盘上写入/读取结构。在一个应该正常工作的程序中。

大错误-a-boo是编译器将在字段之间插入填充以保持数据对齐的事实。这使得代码不那么便携,好像模块编译时具有不同的对齐要求,结构字面上可以是不同的大小,将固定大小的假设抛到门外。

我会倾向于某种序列化库中的旧版本。

答案 3 :(得分:1)

另一种方法是重载运算符<<和运算符>>对于结构,它知道如何保存/加载自己。这将减少知道读/写节点的位置的问题。理论上,您的左右子字段可以是寻址到节点实际所在位置的地址,而新字段可以保存当前节点的搜索位置。

答案 4 :(得分:1)

在实现自己的序列化方法时,您必须做出的第一个决定是您是希望磁盘上的数据是二进制格式还是文本格式。

我发现更容易实现保存为二进制格式的功能。实现它所需的功能数量很少。您需要实现可以编写基本类型的函数,编译时已知大小的数组,动态数组和字符串。其他所有东西都可以建立在其他之上。

这里的内容与我最近投入生产代码的内容非常接近。

#include <cstring>
#include <fstream>
#include <cstddef>
#include <stdexcept>

// Class to write to a stream
struct Writer
{
   std::ostream& out_;

   Writer(std::ostream& out) : out_(out) {}

   // Write the fundamental types
   template <typename T>
      void write(T number)
      {
         out_.write(reinterpret_cast<char const*>(&number), sizeof(number));
         if (!out_ )
         {
            throw std::runtime_error("Unable to write a number");
         }
      }

   // Write arrays whose size is known at compile time
   template <typename T, uint64_t N>
      void write(T (&array)[N])
      {
         for(uint64_t i = 0; i < N; ++i )
         {
            write(array[i]);
         }
      }

   // Write dynamic arrays
   template <typename T>
      void write(T array[], uint64_t size)
      {
         write(size);
         for(uint64_t i = 0; i < size; ++i )
         {
            write(array[i]);
         }
      }

   // Write strings
   void write(std::string const& str)
   {
      write(str.c_str(), str.size());
   }

   void write(char const* str)
   {
      write(str, std::strlen(str));
   }
};
// Class to read from a stream
struct Reader
{
   std::ifstream& in_;
   Reader(std::ifstream& in) : in_(in) {}

   template <typename T>
      void read(T& number)
      {
         in_.read(reinterpret_cast<char*>(&number), sizeof(number));
         if (!in_ )
         {
            throw std::runtime_error("Unable to read a number.");
         }
      }

   template <typename T, uint64_t N>
      void read(T (&array)[N])
      {
         for(uint64_t i = 0; i < N; ++i )
         {
            read(array[i]);
         }
      }

   template <typename T>
      void read(T*& array)
      {
         uint64_t size;
         read(size);
         array = new T[size];
         for(uint64_t i = 0; i < size; ++i )
         {
            read(array[i]);
         }
      }

   void read(std::string& str)
   {
      char* s;
      read(s);
      str = s;
      delete [] s;
   }
};
// Test the code.

#include <iostream>

void writeData(std::string const& file)
{
   std::ofstream out(file);
   Writer w(out);
   w.write(10);
   w.write(20.f);
   w.write(200.456);
   w.write("Test String");
}

void readData(std::string const& file)
{
   std::ifstream in(file);
   Reader r(in);

   int i;
   r.read(i);
   std::cout << "i: " << i << std::endl;

   float f;
   r.read(f);
   std::cout << "f: " << f << std::endl;

   double d;
   r.read(d);
   std::cout << "d: " << d << std::endl;

   std::string s;
   r.read(s);
   std::cout << "s: " << s << std::endl;
}

void testWriteAndRead(std::string const& file)
{
   writeData(file);
   readData(file);
}

int main()
{
   testWriteAndRead("test.bin");
   return 0;
}

输出:

i: 10
f: 20
d: 200.456
s: Test String

很容易实现编写和阅读Node的能力。

void write(Writer& w, Node const& n)
{
    w.write(n.leftChild);
    w.write(n.rightChild);
    w.write(n.value);
    w.write(n.count);
    w.write(n.balanceFactor);
}

void read(Reader& r, Node& n)
{
    r.read(n.leftChild);
    r.read(n.rightChild);
    r.read(n.value);
    r.read(n.count);
    r.read(n.balanceFactor);
}

答案 5 :(得分:0)

您指的过程称为序列化。我会在http://uscilab.github.io/cereal/

推荐Cereal

它支持 json xml 二进制序列化,并且非常易于使用(有很好的例子) )。

(不幸的是,它不支持我最喜欢的格式 yaml