微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么当我尝试使用读写时 fstream 无法正常工作?

如何解决为什么当我尝试使用读写时 fstream 无法正常工作?

我正在尝试使用 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;
}

有什么想法吗?

解决方法

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

  1. std::map<> 并非微不足道,无法按照您尝试的方式进行序列化 去做吧。您必须自己将其序列化 - 逐项。
  2. dbfile.clear(); 只清除流的错误标志 可能不是您想要做的。
  3. Load() 应该定位读取光标,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;
};
,

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

但它正在创建一个空文件

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


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

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

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

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

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

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

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 格式(逗号分隔值文件)存储,以便轻松导入您的数据,例如如果您将联系人数据存储在数据库中,则在电子邮件软件中。

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

#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;
}
,

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

为了让它起作用:

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

还有这个:

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

...我什至不确定需要什么。想象一个可能的实现:

std::map<str,str> db;

它不会只是一个以空字节结尾的原始数据流。这将是相当复杂的,它将有指针。即使你能以某种方式告诉

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

dbFile

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

总共可能有 20 或 30 行代码。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。