1

我有一个应用程序从另一个应用程序同时写入的文本文件中读取。读取文件的应用程序使用 C - fopen 以文本模式打开它。文件中的行可能很大,例如最大 100 兆。出于这个原因,我们有一个函数,它使用 fgets 从文件中读取 4K 的块并将其附加到字符串对象中,直到它设法读取完整的行。进行读取尝试时,正在写入文件的应用程序可能已写入部分行。Out 自定义 ReadLine 函数通过检测文件结尾、将文件指针重新设置到最后一个已知的正确位置并丢弃已经读取的文本来处理这种情况。

该函数如下所示:

#define MAX_BUF_SIZE 4096

bool ReadLine(FILE* fp, std::string& result, bool& isEof) {

    result.clear();
    long const lastOffset = ftell(fp);
    bool hasReadOneLine = false;
    isEof = false;

    debug_print(lastOffset);

    while (!hasReadOneLine && !isEof) {

        char dataRead[MAX_BUF_SIZE];
        memset(dataRead, 0, sizeof(dataRead));

        if (fgets(dataRead, MAX_BUF_SIZE, fp) == NULL) {

            if (feof(fp)) {
                debug_print("Flag 1");
                isEof = true;
            } else {
                debug_print("Flag 2");
                result.clear();
                fseek(fp, lastOffset, SEEK_SET); //reset the file pointer to where it was
                return false;
            }

        }

        result += dataRead;
        hasReadOneLine = (result[result.length()-1] == '\n');

    } // end loop

    if (!hasReadOneLine) {
        debug_print("Flag 3");
        result.clear();
        fseek(fp, lastOffset, SEEK_SET); //reset the file pointer to where it was
        return false;
    }

    // drop the new-line character ...
    if (result[ result.length()-1] == '\n') {
        result.resize(result.size() - 1);
    }

    return true;

}

问题:我遇到了这样一种情况,在从文件中读取整行后,ReadLine() 函数在再次调用读取下一行时从先前读取的行返回最后一个块。我记录了 ftell() 返回的 lastOffset 的值,并注意到在这种罕见的情况下,fgets 没有将文件指针移动到它读取的行的末尾。

我添加了一些调试行,但在我的情况下,唯一打印的是 lastOffset 值。

在ReadLine返回不完整行的调用中,lastOffset的值为:21563617 不完整行的长度为:920

在返回完整行之前的调用中,lastOffset 的值为:21442207 上一次调用中读取的行长度为:122331(包括换行)

我的问题是:有没有人遇到过类似的问题?您对可能出现的问题有何看法?我不一定要寻找一个完整的答案,而只是一些关于可能出错的指示。

** 更新 **

我设法用一个小实用程序重现了这个问题,以 4K 的块写入一个文件,睡眠间隔为 10 毫秒,而另一个程序(使用上述功能)同时从同一个文件读取。

看起来在上面的函数中执行 fseek() 来重新设置文件指针是一个不好的选择,因为将文件指针重新设置到以前的位置并不一定会清除 C 库自己的内部缓冲区。我仍然不完全相信这种解释,因为在某些情况下(重现情况)文件指针重置从未发生过。

无论如何,我在网上做了一些搜索,一些线程似乎建议使用较低级别的流并处理库本身的缓冲。所以我改变了上面函数的实现和它的其他助手来做到这一点。我现在使用 Windows 的 _sopen_s()/_read()/_lseek() 和 Linux/Solaris 的标准 POSIX 接口来执行较低级别的 IO 处理。通过这些更改,它似乎起作用了,我不再看到这个问题了。

谢谢大家的时间。非常感谢您的所有意见。

苏曼

** 更新 2 **

好吧,现在我肯定知道原因了。问题是如果文件以文本模式打开,则 ftell() 和 fseek() 是不可靠的。如果文件以二进制模式打开,则上面的功能可以正常工作。

这是其他人之前发现此问题的文章的链接:http ://arstechnica.com/civis/viewtopic.php?f=20&t=420490

这是一件好事,因为现在我有一个需要更改 1 行而不是 200 行的修复程序!:-)

4

1 回答 1

0

如果您的最大行大小小于MAX_BUF_SIZE,那么您可能需要考虑一种替代解决方案,该解决方案将大大简化您的实现。简而言之,使用fread代替fgets

void ReadLine(FILE* fp, std::string& result, bool& isEof)
{
    static char dataRead[MAX_BUF_SIZE] = {0};
    static int  dataindex = 0;
    int datalength = fread(dataRead,MAX_BUF_SIZE-dataindex,1,fp);
    for (int i=0; dataRead[i]!='\n'; i++)
        result += dataRead[i];
    dataindex = result.length()+1;
    memmove(dataRead,dataRead+dataindex,datalength-dataindex);
    isEof = feof(fp);
}

笔记:

  1. 此实现假定最后一行(因此文件本身)以换行符结尾。

  2. 您可以使用dataRead/dataindex作为循环缓冲区以避免memmove操作。

于 2014-01-27T20:00:23.667 回答