我正在尝试用C ++编写一个应用程序,用户可以在某个日期范围内询问某个天气参数,该程序会从互联网上找到该信息并将其写入文本文件。因此,用户可以在2009年8月2日到2009年8月10日之间每天要求高温等温度。然后应用程序会发出类似这样的文本文件:
Month, Date, Year, High
8 2 2009 80.3
8 3 2009 76.9
...
8 10 2009 68.4
我已经获得了网页,将HTML解析为有意义的值,并将这些值写入数据库(txt文件)中。我还写了一个函数
insert(std::iostream& database, Day day); //Day is a class I defined that contains all the weather information
将找到这一天所属的位置,并将其插入中间。我测试了这个功能,它的工作方式与它应该完全一样。
我现在正在尝试编写一个执行此操作的函数:
void updateDatabase(std::iostream& database, Day start, Day end)
{
Day currentDay = start;
while (currentDay.comesBefore(end))
{
if (currentDay.notInDatabase(database))
insert(database, currentDay);
currentDay = currentDay.nextDay();
}
}
但遗憾的是,如果我每个程序调用一次,insert()函数只能正常工作。如果我尝试连续两次调用insert(),(或三次,四次或五次),只有最后一天会显示在我的文本文件中。
这是可以重现我的问题但仍然运行的最小可能代码量。
#include <iostream>
#include <fstream>
#include <string>
const std::string FOLDER = "/Users/Jimmy/Desktop/WeatherApp/";
const std::string DATABASE_NAME = FOLDER + "database.txt";
class day
{
public:
int date;
int month;
int year;
bool comesBefore(int month, int date, int year);
day(int month, int date, int year)
{
this->month = month;
this->date = date;
this->year = year;
}
};
void writeToDatabase(std::iostream& file, day today, bool end = true);
void insertDay(std::iostream& file, day today);
int main()
{
std::fstream database;
database.open(DATABASE_NAME);
if (database.fail())
{
std::cout << "Cannot find database.\n";
exit(1);
}
day second(1, 2, 2000);
insertDay(database, second);
std::cout << "First day inserted. Press enter to insert second day.\n";
std::cin.get();
day third(1, 3, 2000);
insertDay(database, third);
std::cout << "Done!\n";
return 0;
}
bool day::comesBefore(int month, int day, int year)
{
if (this->year < year)
return true;
if (this->year > year)
return false;
//We can assume this->year == year.
if (this->month < month)
return true;
if (this->month > month)
return false;
//We can also assume this->month == month
return (this->date < day);
}
void writeToDatabase(std::iostream& file, day today, bool end)
{
if (end) //Are we writing at the current cursor position or the end of the file?
file.seekg(0, std::ios::end);
file << today.month << '\t' << today.date << '\t' << today.year << '\n';
return;
}
void insertDay(std::iostream& file, day today)
{
//Clear flags, and set cursor at beggining
file.clear();
file.seekg(0, std::ios::beg);
int date, month, year;
long long positionToInsert = 0;
while (!file.eof())
{
file >> month >> date >> year;
//std::cout << month << date << year << '\n';
if (today.comesBefore(month, date, year))
{
//We found the first day that comes after the day we are inserting
//Now read backwards until we hit a newline character
file.unget();
char c = '\0';
while (c != '\n')
{
file.unget();
c = file.get();
file.unget();
}
positionToInsert = file.tellg();
break;
}
}
if (file.eof())
{
//We hit the end of the file. The day we are inserting is after every day we have. Write at the end.
file.clear();
writeToDatabase(file, today);
return;
}
file.clear();
file.seekg(0, std::ios::beg);
std::fstream tempFile;
std::string tempFileName = FOLDER + "tempfile.txt";
std::string terminalCommand = "> " + tempFileName;
//Send the command "> /Users/Jimmy/Desktop/WeatherApp/tempfile.txt" to the terminal.
//This will empty the file if it exists, and create it if it does not.
system(terminalCommand.c_str());
tempFile.open(tempFileName);
if (tempFile.fail())
{
std::cout << "Failure!\n";
exit(1);
}
int cursorPos = 0;
while (cursorPos++ < positionToInsert)
{
char c = file.get();
tempFile.put(c);
}
tempFile.put('\n'); //To keep the alignment right.
writeToDatabase(tempFile, today, false);
file.get();
char c = file.get();
while (!file.eof())
{
tempFile.put(c);
c = file.get();
}
terminalCommand = "mv " + tempFileName + " " + DATABASE_NAME;
//Sends the command "mv <tempFileName> <databaseName>" to the terminal.
//This command will move the contents of the first file (tempfile) into the second file (database)
//and then delete the old first file (tempfile)
system(terminalCommand.c_str());
return;
}
我在main中添加了cin.get()部分,所以我可以在每次insert()调用之前和之后查看我的数据库。这是编译/运行之前的数据库:
1 1 2000
1 4 2000
这是在通过cin.get()进入/移动之前的数据库:
1 1 2000
1 2 2000
1 4 2000
这是继过cin.get()之后的数据库,我的程序退出:
1 1 2000
1 3 2000
1 4 2000
我已经更改了插入的日期,插入的日期数,两个日期之间的距离以及运行程序之前数据库的初始大小,但我总是得到相同的结果。在每次调用insert()之后,数据库就像是唯一一次调用insert一样。但是,如果我多次运行该程序,则文本文件会继续增长。如果我尝试在每次编译/运行时多次调用insert,我只会遇到此问题。所以如果我要运行这个程序5次:
int main()
{
std::fstream database;
database.open(DATABASE_NAME);
if (database.fail())
{
std::cout << "Cannot find database.\n";
exit(1);
}
day today(1, 2, 2000);
insertDay(database, today);
std::cout << "Done!\n";
return 0;
}
我的数据库最终看起来像这样:
1 1 2000
1 2 2000
1 2 2000
1 2 2000
1 2 2000
1 2 2000
1 4 2000
我怀疑它是fstream.clear(),fstream.seekg()和fstream.eof()的问题,或者可能是关闭/重新打开文件。但我没有采取任何措施来解决这个问题。
此外,值得注意的是,这不会在Windows计算机上运行。它应该在linux上没问题,但我只在Mac上测试过,所以我错了。它使用bash来创建/删除/重命名/移动文件。
任何帮助(即使只是朝着正确的方向轻推)都非常感激。我已经把头发拉过这一段了一段时间。另外,我知道SO不喜欢代码转储,所以我大大简化了问题。我的完整程序是超过700行和10个不同的文件,这是我可以得到它的最短时间,同时仍然可以了解它。
答案 0 :(得分:2)
您遇到的问题与处理文件的方式有关:当您mv
文件时,旧文件本身不会被覆盖;相反,它是 unlinked (“已删除”),并且在该位置创建了一个新文件。
在类Unix操作系统上,您仍然可以保留未链接文件的句柄:它只是无法使用路径访问。这就是为什么在Unix上删除一个仍然打开的文件是完全可以的,这与Windows不同:the file still exists after you have unlinked it, at least until all the file descriptors have been closed。这意味着database
根本没有改变:它仍然指向您的旧文件并包含相同的内容。
一个简单的解决方法是关闭并重新打开该文件。 (从实际角度来看,使用现成的解决方案,例如Sqlite可能要好得多。)