将文本插入文件只能运行一次

时间:2014-12-20 05:55:24

标签: c++ database bash file-io ifstream

我的目标:

我正在尝试用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个不同的文件,这是我可以得到它的最短时间,同时仍然可以了解它。

1 个答案:

答案 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可能要好得多。)