使用GZIPOutputStream压缩byte[]报错:Unexpected end of ZLIB输入流
问题原因及解决方法
你的问题出在GZIP压缩流未完成收尾操作,导致压缩后的字节数组不完整,解压时无法识别GZIP的结束标记,从而抛出EOFException。
具体分析
GZIPOutputStream在完成数据写入后,需要写入GZIP格式的结束校验信息(包括CRC校验值、原始数据长度等),这些信息是解压时验证数据完整性的必要部分。你当前只调用了os.flush(),但flush()仅会将缓冲区中的数据输出到ByteArrayOutputStream,并不会触发GZIP的收尾逻辑;同时你在try-with-resources块内部就调用了baos.toByteArray(),此时GZIPOutputStream还未被关闭(try-with-resources会在块执行完毕后才自动关闭资源),因此压缩数据缺少关键的结束标识。
两种修复方案
方案1:主动调用finish()完成压缩流
在数据复制完成后,显式调用GZIPOutputStream.finish()触发收尾逻辑,再获取压缩后的字节数组:
@Test public void testCompressBytes() throws IOException { final byte[] uncompressed = RandomStringUtils.randomAlphanumeric(100000 /* 100 kb */).getBytes(); // compress byte[] compressed; try (InputStream is = new ByteArrayInputStream(uncompressed); ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream os = new GZIPOutputStream(baos)) { IOUtils.copy(is, os); os.finish(); // 关键:完成GZIP压缩流,写入结束校验信息 compressed = baos.toByteArray(); } System.out.println("Size before compression = " + uncompressed.length + ", after = " + compressed.length); // decompress back byte[] decompressedBack; try (InputStream is = new GZIPInputStream(new ByteArrayInputStream(compressed)); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { IOUtils.copy(is, baos); decompressedBack = baos.toByteArray(); } assertArrayEquals(uncompressed, decompressedBack); }
方案2:利用try-with-resources自动关闭的特性
将ByteArrayOutputStream的声明移到try-with-resources块外,等GZIPOutputStream被自动关闭(关闭时会自动调用finish())后,再获取完整的压缩数据:
@Test public void testCompressBytes() throws IOException { final byte[] uncompressed = RandomStringUtils.randomAlphanumeric(100000 /* 100 kb */).getBytes(); // compress byte[] compressed; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (InputStream is = new ByteArrayInputStream(uncompressed); OutputStream os = new GZIPOutputStream(baos)) { IOUtils.copy(is, os); } // try块结束后,GZIPOutputStream自动关闭,完成收尾 compressed = baos.toByteArray(); System.out.println("Size before compression = " + uncompressed.length + ", after = " + compressed.length); // decompress back byte[] decompressedBack; try (InputStream is = new GZIPInputStream(new ByteArrayInputStream(compressed)); ByteArrayOutputStream baosDecompress = new ByteArrayOutputStream()) { IOUtils.copy(is, baosDecompress); decompressedBack = baosDecompress.toByteArray(); } assertArrayEquals(uncompressed, decompressedBack); }
为什么flush()不行?
flush()的作用是将输出流缓冲区中的数据立即写入底层流,但不会触发GZIP格式的收尾操作。只有finish()或close()方法才会写入GZIP所需的结束校验信息,确保压缩数据的完整性,让GZIPInputStream能够正确识别压缩流的结束位置。
内容的提问来源于stack exchange,提问作者Leszek Pachura
相关产品推荐
相关产品推荐

