Java 中明显的 gzip 文本不是 gzip 格式

Not a gzip format for a obvious gzip text in Java

我一直在尝试解压以 GZIP 格式压缩的文本 下面是我实现的方法

private byte[] decompress(String compressed) throws Exception {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    ByteArrayInputStream in = new 
        ByteArrayInputStream(compressed.getBytes(StandardCharsets.UTF_8));
    GZIPInputStream ungzip = new GZIPInputStream(in);
    byte[] buffer = new byte[256];
    int n;
    while ((n = ungzip.read(buffer)) >= 0) {
        out.write(buffer, 0, n);
    }
    return out.toByteArray();
}

现在我正在测试以下压缩文本的解决方案:

H4sIAAAAAAAACjM0MjYxBQAcOvXLBQAAAA==

并且没有 gzip 格式异常。 我尝试了不同的方法,但仍然存在此错误。也许有人知道我做错了什么?

这不是 gzip 格式。一般来说,压缩的 不能是 字符串(因为压缩数据是字节,而字符串不是字节。一些语言/教程/1980 年代的想法将 2 混为一谈,但这是 2020 年代。我们别再那样了。英文的字符比英文多。

看起来可能发生了以下情况:

  • 有人有一些数据。
  • 他们压缩了它。
  • 然后他们使用 Base64 编码将 gzip 流(字节)转换为字符。
  • 他们发给你了。
  • 您现在想要返回数据。

鉴于发生了 2 次转换(首先 gzip,然后 base64),您还需要进行 2 次反向转换。您需要:

  • 获取输入字符串,并将其反 base64,得到字节。
  • 然后您需要获取这些字节并解压缩它们。
  • 现在您已经恢复了原始数据。

因此:

byte[] gzipped = java.util.Base64.getDecoder().decode(compressed);
var in = new GZIPInputStream(new ByteArrayInputStream(gzipped));
return in.readAllBytes();

注:

像这样将数据从输入流推送到输出流是一种资源浪费和一堆挑剔的代码。这个不用写了;只需调用 readAllBytes.

如果传入的 Base64 很大,有一些方法可以以流方式进行。这将要求此方法采用 Reader(而不是无法流式传输的 String),并且 return 是 InputStream 而不是 byte[] .当然如果输入不是特别大,也没有必要。上面的方法有点浪费——base64-ed 数据,and un-base64ed 数据,and 解压后的数据都在内存中同时,您无法避免这种情况,垃圾收集器也无法在两者之间收集任何这些东西(因为调用者很可能继续引用该 base64 字符串)。

换句话说,如果压缩率为 50%,并且未压缩的总数据大小为 100MB,则此方法需要的时间超过:

100MB(未压缩)+ 50MB(压缩)+ 50*4/3 = 67MB(压缩但 base64ed)= ~ 217MB 内存。

您比我们更清楚您的 VM 运行 有多少堆,以及输入数据可能达到多大。

注意:Base64 传输效率极低,每 3 个字节的输入数据占用 4 个字节的 base64 内容,如果数据传输采用 UTF-16,则每 3 个字节占用 8 个字节,甚至。哎哟。考虑到内容是 GZip 压缩的,这感觉有点愚蠢:首先我们煞费苦心地减小了这个东西的大小,然后我们可能没有充分的理由随便将它膨胀 33%。您可能想检查导致您出现此问题的 'pipe',也许您可​​以...消除此的 base64 方面。

例如,如果您有一个有线协议并且有人认为 JSON 是个好主意,那么..简单地..不要。如果您需要传输一堆原始数据,JSON 不是一个好主意。使用protobuf,或者发送JSON和blob等的组合