使用 std::filebuf 未反映文件大小调整

File resize not reflected using std::filebuf

我创建了一个空文件,我正在尝试调整它的大小。除了打开模式外,我的调整大小和创建代码非常相似。初始创建按预期工作。调整大小的代码执行正常,但没有反映文件调整大小。

这是我的代码:

void resize()
{
    std::filebuf fileBuffer;
    fileBuffer.open(filePath, ios::out | ios::binary | ios::app);
    if (fileBuffer.is_open())
    {
        fileBuffer.pubseekoff((2 * fileSize) - 1, ios::beg);
        auto x = fileBuffer.sputc(0);
        fileBuffer.close();
    }
}

int main()
{
    std::filebuf fileBuffer;
    fileBuffer.open(filePath, ios::out | ios::trunc | ios::binary);
    if (fileBuffer.is_open())
    {
        fileBuffer.pubseekoff(fileSize - 1, ios::beg);
        fileBuffer.sputc(0);
        fileBuffer.close();
    }
    resize(); // Doesn't work

    resizeWithData(); // Works
}

当我尝试通过将实际数据推送到文件中来调整文件大小时,如下所示,它起作用了:

void resizeWithData()
{
    ofstream fstr(filePath, ios::out | ios::binary | ios::app);
    if (fstr.is_open())
    {
        const auto emptySec = make_unique<char[]>(fileSize);
        memset(emptySec.get(), 0, fileSize);
        fstr.write(emptySec.get(), fileSize);
        fstr.close();
    }
}

为什么会有这种差异?我的实际场景涉及创建和调整非常大的文件(1 GB 或更多),所以我希望我可以避免为空数据分配内存,如上面的代码所示(因为它真的很慢)

P.S. : 我正在开发 windows 7 with VS2013 update 4

resize() 失败的原因是因为您在 app 模式下打开了文件。每次写入此流时,实现都会将写入位置重新同步到文件末尾。对 pubseekoff 本身的调用,调用 seekoff,根据 fseek 定义,不写入任何内容,它只是重新定位文件位置指示器。

从您在 main 中打开的文件中可以看出,如果没有 app 标志,sputc 将立即写入当前位置,尽管这可能无法得到严格保证。

大多数basic_filebuf操作都是根据C文件函数定义的,其中似乎没有提到当文件位置指示器超出末尾时调用fputc时会发生什么的文件。 POSIX 但是明确表示这会将零写入该位置,您可能会发现大多数实现都支持此定义。

不同之处在于,在第一个示例中,您截断了文件。如果您在 resize 中将 ios::app 更改为 ios::trunc,文件将具有所需的大小(至少在我的系统上是这样)。有趣的是,我在标准中找不到任何关于它应该如何表现的声明,只有一些声明它的行为相当于 C 风格的调用 api。对于 C 风格 API 我只在 cpp reference 上找到以下注释:

POSIX allows seeking beyond the existing end of file. If an output is performed after this seek, any read from the gap will return zero bytes. Where supported by the filesystem, this creates a sparse file.

IMO,最后你的 fileBuffer.pubseekoff(fileSize - 1, ios::beg); 调用也可能失败了。

但是,分配 1GB 的零只是为了将它们转储到文件中是一种浪费。我会使用带有重复写入调用的缓冲区:

#include <fstream>
#include <iostream>
#include <array>

using std::ios;

std::string filePath("test.file");
size_t fileSize = 1024*1024;

void resize(const std::string& path, std::size_t newSize){
  const std::size_t buffSize = 1024*1024;
  std::array<char, buffSize> buff{};
  std::filebuf fileBuffer;

  fileBuffer.open(path, ios::out | ios::binary | ios::app);

  auto reminder = newSize - fileBuffer.pubseekoff(0, ios::cur);
  while( reminder > buffSize ){
    fileBuffer.sputn(buff.data(), buffSize);
    reminder -= buffSize;
  }
  fileBuffer.sputn(buff.data(), reminder);

  fileBuffer.close();
}

int main()
{
  std::filebuf fileBuffer;
  fileBuffer.open(filePath, ios::out | ios::binary | ios::trunc);
  if (fileBuffer.is_open())
  {
    fileBuffer.pubseekoff(fileSize - 1, ios::beg);
    fileBuffer.sputc(0);
    fileBuffer.close();
  }
  resize(filePath, 3500000);
}