使用缓冲区从未知大小的文件中读取

6

我正在尝试从文件中读取块,但是遇到了问题。

char* inputBuffer = new char[blockSize]
while (inputFile.read(inputBuffer, blockSize)) {
    int i = inputFile.gcount();
//Do stuff
}

假设我们的块大小为1024字节,文件大小为24.3 KiB。读取第23个块后,还剩下0.3 KiB要读取。我想读取这0.3 KiB,事实上我稍后会使用gcount()来知道read(...)修改了多少缓冲区(以防它较少)。但是当访问第24个块时,read(...)返回一个值,使得程序不进入循环,显然是因为未读取的字节数小于缓冲区大小。我该怎么办?
3个回答

3

我认为你在另一个回答的评论中提到的Konrad Rudolf指出了读取文件直到EOF的问题。如果由于其他错误而永远无法达到EOF,那么程序将会进入无限循环。因此,请遵循他的建议,并修改以解决你所发现的问题。一种方法是:

bool okay=true;
while ( okay ) {
    okay = inputFile.read(inputBuffer, blockSize);
    int i = inputFile.gcount();
    if( i ) {
        //Do stuff
    }
}

编辑:由于我的答案已被接受,我正在进行编辑,以使其尽可能有用。事实证明,我的bool okay是相当不必要的(请参见ferosekhanj的答案)。直接测试inputFile的值更好,这也有一个优点,即如果文件没有正确打开,您可以优雅地避免进入循环。因此,我认为这是解决此问题的规范解决方案。

inputFile.open( "test.txt", ios::binary );
while ( inputFile ) {
    inputFile.read( inputBuffer, blockSize );
    int i = inputFile.gcount();
    if( i ) {
        //Do stuff
    }
}

现在,最后一次你//做事情,我会比块大小小,除非文件恰好是块大小字节长。
Konrad Rudolf的答案这里也很好,它的优点是.gcount()只调用了一次,在循环之外,但缺点是真正需要数据处理放在一个单独的函数中,以避免重复。

你应该在“okay = inputFile.read(...)”后面加一个if(okay),以确保程序永远不会使用无效的数据。这样做,我就可以将其标记为已接受的答案。 - Guido Tarsia
@Erandos,不行,因为那样你又回到了起点——你无法处理最后一个子块!相反,也许可以加上if(i),这样只有在有数据时才会执行操作。 - Bill Forster
你说得对。我仍然认为应该有一个“if (lessThanBufferSizeFlag)”语句。但我不知道如何获取该标志的值。 - Guido Tarsia
@Erandos,我增加了if(i)。只有在成功读取数据时,i才会非零。当i非零但小于blockSize时,唯一的情况是读取最后一个奇数大小的片段,但现在你可以处理这些数据,这才是你真正想要的。 - Bill Forster
1
if (i>0 && i<blockSize) 与 if (lessThanBufferSizeFlag) 相同。在 API 中添加过多的标志是适得其反的,将 API 的细节完整、优雅、必要和充分地呈现出来是一门艺术! - Bill Forster

3
@Konrad Rudolph提到的解决方案是检查流对象本身,因为这包括检查eof和错误条件。inputFile.read()返回的是inputFile本身,因此您可以编写如下代码:
while(inputFile.read())

但这种方法并不总是有效的。它失效的情况就是你的情况。一个正确的解决方案将是按照以下方式编写:
char* inputBuffer = new char[blockSize]
while (inputFile) 
{
    inputFile.read(inputBuffer, blockSize);
    int count = inputFile.gcount();
    //Access the buffer until count bytes
    //Do stuff
}

我认为这就是@Konrad Rudolph在他的帖子中提到的解决方案。根据我以前的CPP经验,我也会像上面那样做。


我同意,实际上这比我的解决方案更好,所以加一分,做得好。 - Bill Forster
我的解决方案与你的相比,唯一(微小)的优势在于我只需要在循环之后检查gcount一次(在循环内它始终等于blockSize)。而且我真的不喜欢执行不必要的操作,即使它们很便宜。话虽如此,一旦处理缓冲区变得非常规(=不止一个语句),你的解决方案就更加优越了。 - Konrad Rudolph

1
但是当它访问第24个块时,read(...)返回一个值,使得程序显然不会进入循环,因为文件中未读取的剩余字节数小于缓冲区大小。
这是因为你的循环有问题。你应该这样做:
while(!inputFile) {
    std::streamsize numBytes = inputFile.readsome(inputBuffer, blockSize);
//Do stuff
}

请注意使用readsome而不是read。该内容与编程有关。

那个循环有问题吗?这位拥有 100K 声望的人说相反的话:https://dev59.com/WljUa4cB1Zd3GeqPSIZ6#6444962 不过我会试一下 readsome。 - Guido Tarsia
1
@Erandros:他还说在while条件中放置读取操作“更易读”,这是我要反驳的。在条件语句中执行实际工作可能是C/C++中已有的习惯用法,但并不一定是好的或易读的。 - Nicol Bolas
我同意“更易读”的问题。但是,如果我读取一些数据失败会发生什么?我将继续使用损坏的数据进行操作。确保这种情况永远不会发生是那段时间的好处。 - Guido Tarsia
2
这不是一个好的解决方案,因为正如Konrad Rudolf在Erandros给出的评论链接中指出的那样,如果inputFile有其他问题,则它永远不会到达eof,你将被困在无限循环中,所以-1抱歉。ferosekhanj提供了最佳答案。 - Bill Forster
@Nicola 一般而言,我同意你的观点,即循环条件不应该执行任何操作。然而,由于这是C++中已经确立的习惯用法,从流中读取数据是一个例外。事实上,这种用法使得代码更易读(因为有经验的程序员会期望这样)。无论如何,仅检查eof是不够的。 - Konrad Rudolph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接