【问题标题】:Why does fstream not working correctly when I'm trying to use read and write?为什么我尝试使用读写时 fstream 无法正常工作?
【发布时间】:2021-03-18 20:14:07
【问题描述】:

我正在尝试使用 fstream 和 map 创建本地文件数据库。 我正在使用 fstream 类进行读写。 但它正在创建一个空文件,我尝试使用运算符 >>、

代码如下:

#include <iostream>
#include <map>
#include <fstream>
#include <string>
using str = std::string;
class MapDB{
    public:
        MapDB(str path){
            this->dbfile.open(path, std::fstream::in | std::fstream::out | std::fstream::trunc);
        }
        void PushValue(str key, str value){
            this->Load();
            this->db[key] = value;
            this->Save();
        }
        str GetValue(str key){
            this->Load();
            return this->db[key];
        }
        void RemoveKey(str key){
            this->db.erase(key);
            this->Save();
        }
        ~MapDB(){
            this->dbfile.close();
        }
    private:
        void Load(){
            //this->dbfile.read((char*)&this->db, sizeof(MapDB));
            this->dbfile >> (char*)&this->db;
        }
        void Save(){
            this->dbfile.clear();
            //this->dbfile.write((char*)&this->db, sizeof(MapDB));
            this->dbfile << (char*)&this->db;
        }
        std::fstream dbfile;
        std::map<str, str> db;
};
int main(int argc, char *argv[]){
    std::map<str, str> mydb;
    MapDB mydata("/path/to/file/data.data");
    mydata.PushValue("key", "value");
    std::cout << mydata.GetValue("key") << std::endl;
}

有什么想法吗?

【问题讨论】:

  • 这也是您对观察到的症状的描述。 “不起作用”尽可能含糊。
  • this-&gt;dbfile &lt;&lt; (char*)&amp;this-&gt;db; 不可能工作。您可能想花一些时间阅读和理解这个主题:https://isocpp.org/wiki/faq/serialization
  • using str = std::string; 有点奇怪(但不是错误或非法的);我推荐using string = std::string;using std::string; 作为更好的选择。
  • 可以省去很多打字: 1) 不要使用this-&gt;,直接访问成员和方法。 2) 使用命名方案来区分成员和参数。
  • 除非方法会修改参数,否则您应该通过 const 引用传递字符串。

标签: c++ database dictionary c++11 fstream


【解决方案1】:

您的代码中存在多个问题:

  1. std::map&lt;&gt; 不是微不足道的,不能按照你尝试的方式序列化 去做吧。您必须自己进行序列化 - 逐项。
  2. dbfile.clear(); 只清除流的错误标志 可能不是你想做的。
  3. Lo​​ad() 应定位读取光标,Save() 应定位 写光标。
  4. 您的构造函数会截断文件。所以没有机会阅读 由 MapDB 的另一个实例编写的东西。 (也许这是故意的)

我不会冒险说这个列表几乎是完整的。希望这个例子能给你一些提示:

class MyDB
{
public:
    // constructor.
    // creates or opens the file in binary mode (because we store binary data like the number of items and, the length of the strings).
    // truncates the file on open if the flag 'truncateOnOpen' is set.
    MyDB(const std::string& filename, bool truncateOnOpen)
        : dbfile(filename, std::fstream::binary | std::fstream::in | std::fstream::out | (truncateOnOpen ? std::fstream::trunc : 0))
    {}

    void Load()
    {
        // drop old database content
        db.clear();

        // position read cursor to the beginning of the file
        dbfile.seekg(0);

        // read the number of entries
        size_t entries = 0;
        dbfile.read(reinterpret_cast<char*>(&entries), sizeof(entries));

        // read key and value for each entry
        std::string key, value;
        for (size_t i = 0; i < entries; ++i)
        {
            readString(&key);
            readString(&value);
            db[key] = value;
        }
    }

    void Save()
    {
        // position the write cursor to the beginning of the file
        dbfile.seekp(0);

        // write thenumber of entries
        size_t entries = db.size();
        dbfile.write(reinterpret_cast<const char*>(&entries), sizeof(entries));

        // write key and value for each entry
        for (auto& it : db)
        {
            writeString(it.first);
            writeString(it.second);
        }
    }

private:
    // reads a single string from the file
    bool readString(std::string* target)
    {
        // read the length of the string stored in the file
        size_t len;
        if (dbfile.read(reinterpret_cast<char*>(&len), sizeof(len)).fail())
            return false;

        // preallocate memory for the string
        target->resize(len);

        // read the string from the file
        return dbfile.read(&target->front(), target->size()).good();
    }

    // writes a single string to the file
    void writeString(const std::string& source)
    {
        // write the length of the string to the file
        size_t len = source.size();
        dbfile.write(reinterpret_cast<char*>(&len), sizeof(len));
        
        // write the string itself to the file
        dbfile.write(source.data(), source.size());
    }

private:
    std::fstream dbfile;

public:
    std::map<std::string, std::string> db;
};

【讨论】:

    【解决方案2】:

    好的,让我们一步一步来:

    但它正在创建一个空文件

    那是因为你正在清空文件 std::fstream::trunc - 删除文件中的所有内容(截断它) 这意味着当您打开 data.data 文件时,您会删除其中的所有数据(如果有的话)。


    'std::fstream' 中有两种标准格式:

    1. 格式是std::ios::binary 格式,data.data 不能用文本编辑器读取,但它紧凑且高效。是的

    2. format 将字符串存储为可打印字符。 在这种情况下,data.data 就像带有通用文件编辑器的 txt 文件一样可读。

    由于@Andreas H. 提供了一个二进制文件格式示例并且您没有使用二进制格式,因此我提供了一个最小的可执行示例,该示例使用可打印字符存储您的数据。

    1. int main(int argc, char *argv[]) 需要 [ ]-括号才能正常工作

    2. 旧 c 风格 (char*)&amp;this-&gt;db; 的类型转换工作正常,Scott Meyers 在“Effective C++”中写道,应该避免旧 c 风格,因为旧 c 类型转换不是类型安全的并且“隐藏”在代码所以不容易找到像static_cast&lt;char*&gt;(...) 在这里您可以看到不同之处 - 两者都在做同样的事情:

    int firstNumber = 1, secondNumber = 2;
    
    double result1 = ((double)firstNumber) /secondNumber;
    double result2 = static_cast<double>(firstNumber)/secondNumber;
    
    std::cout << result1 << std::endl;
    std::cout << result2 << std::endl;
    

    为了提供一个最小的可执行示例和 更好的理解我跳过了这门课;) 应该很容易让您在一个漂亮的界面中解析它。

    
    #include <iostream>
    #include <map>
    #include <fstream>
    #include <string>
    
    int main(int argc, char *argv[])
    {
    
      std::map<std::string, std::string> db{{"Key1","value"},{"Key2","value"}};
      std::fstream mydata;
    
      //PRINT MAP
      for(const auto &[k,v] : db)
        std::cout << k << " " << v << std::endl;
    
      //WRITE MAP TO FILE
      //Open File in write mode
      mydata.open("data.data", std::ios::out);
      if(!mydata.is_open())
      std::cout << "Could not open file!" << std::endl;
    
      //Write data to file
      for(const auto &[k,v] : db)
        mydata << k <<" " << v << std::endl;
      //Close file
      mydata.close();
    
      //proof of concept, DESTROY MAP VALUES
      db.clear();
    
      //READ MAP BACK IN FROM FILE
      std::string key , value;
    
      //Open Data in read mode
      mydata.open("data.data", std::ios::in);
      if(!mydata.is_open())
        std::cout << "Could not open file!" << std::endl;
    
      //Read data back to map
      while(mydata >> key >> value)
        db[key] = value;
    
      //Close File again
      mydata.close();
    
      //PRINT MAP
      for(const auto &[k,v] : db){
        std::cout << k << " " << v << std::endl;
      }
    
      return 0;
    }
    

    我希望这会帮助你完成你的项目:)

    顺便说一句。如果您有多个单词作为键或值,则可以使用带分隔符的 getline,并将数据文件存储为 csv 格式(逗号分隔值文件),以便轻松导入数据 f.e.如果您将联系人数据存储在数据库中,则在电子邮件软件中。

    作为一个练习,我写了一个最小的可执行示例,将二进制格式的字符串映射写入文件:

    #include <iostream>
    #include <fstream>
    #include <map>
    #include <string>
    
    #define FILENAME "BinaryMap.bin"
    
    int main()
    {
      std::fstream file;
      std::string key{"Key One"}, value{"Value One"};
      std::map<std::string, std::string> map{{key,value},{"Key Two","Value Two"}};
    
    
      //Print Map
      for(const auto &[key,value] : map){
        std::cout << key << " " << value << '\n';
      }
    
      //Open file and check for errors
      file.open(FILENAME, std::ios::binary | std::ios::out);
      if(!file.is_open())
      std::cout << "Could not open file!" << '\n';
    
      //Position write cursor to the beginning of the file
      file.seekp(0);
    
      //Write the number of entries
      size_t entries = map.size();
      file.write(reinterpret_cast<const char*>(&entries), sizeof(entries));
    
      //Write the number of entries
      for(auto& it : map){
        //Write the lenth of the key string to the file
        size_t len = it.first.size();
        file.write(reinterpret_cast<char*>(&len), sizeof(len));
    
        //Write the string itself to the file
        file.write(it.first.data(), it.first.size());
    
        //Write the length of the value string to the file
        len = it.second.size();
        file.write(reinterpret_cast<char*>(&len), sizeof(len));
    
        //Write the value string itself to the file
        file.write(it.second.data(), it.second.size());
      }
      //Close the file
      file.close();
    
      map.clear();
      std::cout << "--------clear---------" << '\n';
    
      //READ STUFF
      file.open(FILENAME, std::ios::binary | std::ios::in);
      if(!file.is_open())
        std::cout << "Could not open file!" << '\n';
    
      // Position read cursor to the beginning
      file.seekg(0);
    
      // read the number of entries
      entries = 0;
      file.read(reinterpret_cast<char*>(&entries), sizeof(entries));
    
      //Read the length of the string stored in the file
      size_t length {};
      for(size_t i = 0; i < entries; ++i){
        //Read the length of the string in the file
        if(file.read(reinterpret_cast<char*>(&length), sizeof(length)).fail())
            std::cout << "Failed to read bin" << '\n';
    
        //Pointer to variable address
        std::string* p_target = &key;
    
        //Preallocate memory for the string
        p_target->resize(length);
    
        //Read string from file
        file.read(&p_target->front(), p_target->size()).good();
    
        //Read the length of the string in the file
        if(file.read(reinterpret_cast<char*>(&length), sizeof(length)).fail())
          std::cout << "Failed to read bin" << '\n';
    
        //Dereference value
        key = *p_target;
    
        //Assign variable address to pointer
        p_target = &value;
    
        //Preallocate memory for the string
        p_target->resize(length);
    
        //Read string from file
        file.read(&p_target->front(), p_target->size()).good();
    
        //Dereference value
        value = *p_target;
    
        //Write key and value to map
        map[key] = value;
      }
    
      //Close the file
      file.close();
    
      for(const auto &[key,value] : map){
        std::cout << key << " " << value << '\n';
      }
    
      std::cout << "Hit 'return' to continue";
      std::cin.get();
      return 0;
    }
    

    【讨论】:

    • @dbit 我希望您在其中一个解决方案中找到了答案。如果您找到了解决方案,请不要忘记将一个答案标记为“已接受”,以便将您的问题从 stackoverflow 队列中清除。
    【解决方案3】:

    不是 fstream 不能正常工作。 fstream 工作正常。

    为此工作:

            this->dbfile >> (char*)&this->db;
    

    还有这个:

            this->dbfile << (char*)&this->db;
    

    ...我什至不确定会采取什么措施。想象一个可能的实现:

    std::map<str, str> db;
    

    它不会只是最后一个空字节的原始数据流。这将是相当复杂的,并且会有指针。即使您可以以某种方式告诉

    相反,您需要以某种方式手动完成,可能通过迭代地图的内容并将它们打印出来,可能像这样:

    dbFile

    当然,如果数据包含换行符,那将是不安全的。但至少你会更接近。然后您可以使用 getline 读取行并解析该行,基于“ == ”作为分隔符将其分成两部分。

    总共大概有 20 或 30 行代码。

    【讨论】:

      猜你喜欢
      • 2021-06-18
      • 2018-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多