如何解决为什么当我尝试使用读写时 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;
}
有什么想法吗?
解决方法
您的代码中存在多个问题:
-
std::map<>
并非微不足道,无法按照您尝试的方式进行序列化 去做吧。您必须自己将其序列化 - 逐项。 -
dbfile.clear();
只清除流的错误标志 可能不是您想要做的。 - Load() 应该定位读取光标,Save() 应该定位 写游标。
- 您的构造函数会截断文件。所以没有机会阅读 由另一个 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'有两种标准格式:
-
format 是
std::ios::binary
格式,data.data
无法用文本编辑器读取,但它紧凑且高效。是 -
format 将字符串存储在可打印的字符中。 在这种情况下,
data.data
就像带有通用文件编辑器的 txt 文件一样可读。
由于@Andreas H. 提供了一个二进制文件格式示例而您没有使用该二进制格式,因此我提供了一个最小的可执行示例,该示例使用可打印的字符存储您的数据。
-
int main(int argc,char *argv[])
需要 [ ] 括号才能正常工作 -
旧 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 举报,一经查实,本站将立刻删除。