使用 gzwrite (zlib) 了解当前压缩文件的大小

Knowing current compressed file size using gzwrite (zlib)

我正在为 C++ 使用 zlib。

引用自 http://refspecs.linuxbase.org/LSB_3.0.0/LSB-PDA/LSB-PDA/zlib-gzwrite-1.html 关于 gzwrite 功能:

The gzwrite() function shall write data to the compressed file referenced by file, which shall have been opened in a write mode (see gzopen() and gzdopen()). On entry, buf shall point to a buffer containing len bytes of uncompressed data. The gzwrite() function shall compress this data and write it to file. The gzwrite() function shall return the number of uncompressed bytes actually written.

我将此解释为 return 值不会告诉我写入时文件变大了多少。只有多少数据被压缩到文件中。

知道文件有多大的唯一方法是关闭它,然后从文件系统读取大小。我有一个要求只继续写入文件,直到它达到一定的大小。不关闭文件能实现吗?

一种解决方法是写入,直到未压缩的大小达到我的限制,然后关闭文件,从文件系统读取大小并据此更新我对文件大小的最佳猜测,然后重新打开文件并继续写作。这会让我在接近尾声时关闭并打开文件几次(因为我接近大小限制)。

另一个解决方法,它会给出更多的估计(这不是我真正想要的)是写入直到未压缩的大小达到限制,关闭文件,从文件系统读取文件大小并计算到目前为止的压缩比。我可以使用此压缩率来计算未压缩文件大小的新限制,其中压缩应该使我降低到压缩文件大小的限制。如果我重复这个估计会有所改善,但同样不是我想要的。

有更好的选择吗?

如果 zlib 可以在文件仍处于打开状态时告诉我压缩文件的大小,则首选选项是。我不明白为什么此时 zlib 中没有此信息,因为压缩是在我调用 gzwrite 时发生的,而不是在我关闭文件时发生的。

您可以使用管道解决此问题。这个想法是将压缩数据写入管道。之后,你从管道的另一端读取数据,计数并写入实际文件。

要进行此设置,您需要先打开要通过简单 open 写入的文件。然后通过 pipe2 创建管道并通过将管道描述符之一传递给 gzdopen:

来初始化 zlib
int out = open("/path/to/file", O_WRONLY | O_CREAT | O_TRUNC);
int p[2];
pipe2(p, O_NONBLOCK);
gzFile zFile = gzdopen(p[0], "w");

现在可以先将数据写入管道,再从管道拼接输出文件:

gzwrite(zFile, buf, 1024); //or any other length
size_t bytesWritten = 0;
do {
    bytesWritten = splice(p[1], NULL, out, NULL, 1024, SPLICE_F_NONBLOCK | SPLICE_F_MORE);
} while(bytesWritten == 1024);

如您所见,您现在有 bytesWritten 告诉您实际写入了多少数据。只需将其汇总到另一个变量中,并在写入尽可能多的数据后立即停止拼接(或者通过将所有内容写入 zFile 来一次拼接,并使用允许的数据量拼接一次store作为第五个参数。如果你不想压缩不必要的数据,只需按上面所示的块进行。

关于 splice 的注释: Splice 是 linux 特定的,基本上只是一个非常有效的副本。您始终可以用简单的 "read and write" 组合替换它,即将数据从 fd[1] 读取到缓冲区,然后将该缓冲区中的数据写入 out - 拼接只是更快,代码更少。

zlib 提供函数 gzoffset(),它完全满足您的要求。

如果由于某种原因您坚持使用超过八年的 zlib 版本,当添加 gzoffset() 时,那么使用 gzdopen() 很容易做到这一点。您使用 fopen()open() 打开输出文件,并提供文件描述符(如果您使用 fopen(),则使用 fileno()dup()),然后提供gzdopen() 的描述符。然后您可以随时使用 ftell()lseek() 来查看写入了多少。注意不要尝试双重关闭描述符。查看 gzdopen().

的评论