在文件中插入文本只工作一次

Inserting text into a file only works once

本文关键字:一次 工作 文件 插入文本      更新时间:2023-10-16

我的目标:

我正在尝试用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 = '';
            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

这是在按 enter/move over cin.get(( 之前的数据库:

1   1   2000
1   2   2000
1   4   2000 

这是我继续使用 cin.get(( 并且我的程序退出后的数据库:

1   1   2000
1   3   2000
1   4   2000

在运行程序之前,我已经更改了要插入的日期,要插入的日期数,两个日期相距的距离以及数据库的初始大小,但是我总是得到相同的结果。每次调用 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 个不同的文件,这大约是我尽可能短的,同时仍然能传达这个想法。

您在这里遇到的问题与您处理文件的方式有关:当您mv文件时,旧文件本身不会被覆盖;相反,它被取消链接("已删除"(并在 is 中创建一个新文件。

在类Unix操作系统上,您仍然可以保留未链接文件的句柄:只是无法使用路径访问它。 这就是为什么在Unix上完全可以删除仍然打开的文件,这与Windows不同:在取消链接后,该文件仍然存在,至少在所有文件描述符关闭之前。 这意味着database根本没有改变:它仍然指向您的旧文件并包含相同的内容。

一个简单的解决方法是关闭并重新打开该文件。 (从实际的角度来看,只使用现成的解决方案(如 Sqlite(可能会好得多。