我应该如何使用GzipOutputStream和BufferedOutputStream?

33

有人能推荐一下,我是应该做类似于这样的事情:

os = new GzipOutputStream(new BufferedOutputStream(...));
或者
os = new BufferedOutputStream(new GzipOutputStream(...));

哪个更有效? 我应该使用BufferedOutputStream吗?

6个回答

33

GZIPOutputStream已经内置了缓冲区,因此不需要在其后加上BufferedOutputStream。gojomo的出色答案已经提供了一些关于在哪里放置缓冲区的指导。

GZIPOutputStream的默认缓冲区大小仅为512字节,因此您将希望通过构造函数参数将其增加到8K甚至64K。BufferedOutputStream的默认缓冲区大小为8K,这就是当将默认的GZIPOutputStream和BufferedOutputStream组合在一起时可以测量出优势的原因。通过适当调整GZIPOutputStream内置的缓冲区大小也可以实现这种优势。

因此,回答您的问题:"我应该使用BufferedOutputStream吗?"→ 不,在您的情况下,不应使用它,而是将GZIPOutputStream的缓冲区设置为至少8K。


5
这只是一半正确的:GZIPOutputStream 内部的输出缓冲区决定了可以一次压缩多少字节,但如果你向其中提供小块字节(或单个字节),它仍会在 Deflater 可以发出一块压缩数据时立即写入支持的输出流,即使输出缓冲区可以容纳更多数据也是如此。但是,如果在 GZIPOutputStream 前面放置第二个缓冲区,确保只写入大块数据,那么这就没问题了。所以答案仍然正确,只是细节比这更复杂一些。 - defnull

31

我应该先使用哪个GzipOutputStreamBufferedOutputStream

对于对象流,我发现将缓冲流包裹在输入和输出的gzip流周围几乎总是更快的。 对于越小的对象,它的表现越好。 在所有情况下,如果性能相同或更好,则无需使用缓冲流。

ois = new ObjectInputStream(new BufferedInputStream(new GZIPInputStream(fis)));
oos = new ObjectOutputStream(new BufferedOutputStream(new GZIPOutputStream(fos)));

然而, 对于文本和直接的字节流,我发现这是一个赌博 -- 带有缓冲流的gzip流只是略微更好。但在所有情况下都比没有缓冲流要好。

reader = new InputStreamReader(new GZIPInputStream(new BufferedInputStream(fis)));
writer = new OutputStreamWriter(new GZIPOutputStream(new BufferedOutputStream(fos)));

我对每个版本进行了20次测试,并剪掉了第一次运行并对其余的结果求平均。我还尝试了缓冲gzip缓冲,这对于对象来说稍微好一些,但对于文本来说则更差了。我没有改变缓冲区大小。


对于对象流,我测试了两个在10多兆字节范围内的序列化对象文件。 对于更大的文件(38MB),读取速度快了85%(0.7秒对比5.6秒),但实际上写入速度略慢(5.9秒对比5.7秒)。 这些对象中有一些大数组,可能意味着更大的写入。

method       crc     date  time    compressed    uncompressed  ratio
defla   eb338650   May 19 16:59      14027543        38366001  63.4%

对于较小的文件(18MB),它在读取方面快了75%(1.6秒与6.1秒之间),在写入方面快了40%(2.8秒与4.7秒之间)。 它包含大量小对象。

method       crc     date  time    compressed    uncompressed  ratio
defla   92c9d529   May 19 16:56       6676006        17890857  62.7%

我使用了一个64MB的CSV文本文件作为文本读写器。用缓冲流包装gzip流时,读取速度比原始方法快11%(950与1070毫秒),写入速度稍微更快一些(7.9秒与8.1秒)。

method       crc     date  time    compressed    uncompressed  ratio
defla   c6b72e34   May 20 09:16      22560860        63465800  64.5%

很遗憾没有看到确切的输入数据和标准差,否则结果就无法再现。 - darw

13

缓冲有助于在最终数据目的地最好以比您的代码推送的更大块的方式进行读取/写入时。因此,通常希望将缓冲尽可能靠近要求较大块的地方。在您的示例中,这是省略的“...”,所以请使用GzipOutputStream包装BufferedOutputStream。同时,根据测试结果调整BufferedOutputStream缓冲区大小,使其与目标最佳匹配。

我怀疑外部的BufferedOutputStream如果有的话,不会对没有显式缓冲的情况产生太大帮助。为什么呢?不管外部缓冲是否存在,GzipOutputStream都会以相同大小的块将其写入“...”。因此,“...”无法进行优化;您只能使用GzipOutputStream write()函数的固定块大小。

还请注意,通过缓冲压缩数据而不是未压缩数据,可以更有效地利用内存。如果您的数据经常实现6倍压缩,则“内部”缓冲等效于6倍大的“外部”缓冲。


5

通常情况下,在使用FileOutputStream(假设这是...所代表的)时,您需要将缓冲区设置得比较靠近以避免过多地调用操作系统和频繁的磁盘访问。然而,如果您要向GZIPOutputStream写入大量小块,则可能也会从GZIPOS周围的缓冲区中受益。原因是GZIPOS中的write方法是同步的,这还涉及到其他几个同步调用和一些本地(JNI)调用(用于更新CRC32并执行实际压缩)。所有这些都会增加每次调用的额外开销。因此,在这种情况下,我建议您同时使用两个缓冲区会更有益。


1
我建议您尝试一个简单的基准测试,计时压缩大文件需要多长时间,并查看是否有很大差异。GzipOutputStream确实具有缓冲,但它是较小的缓冲区。我会使用64K缓冲区来进行第一项尝试,但您可能会发现两个都做更好。

-1

阅读javadoc,您会发现BIS用于缓冲从某些原始源读取的字节。一旦获得所需的原始字节,您需要对其进行压缩,因此将BIS包装在GIS中。缓冲GZIP的输出毫无意义,因为需要考虑缓冲GZIP的问题,那么谁来做呢?

new GzipInputStream( new BufferedInputStream ( new FileInputXXX

2
你想要将它们压缩,所以你用一个GIS来封装BIS - GIS不会压缩。它反而是解压的。顺便说一下,我在努力理解你答案后半部分的意思。 - bacar

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