如何检查InputStream是否被Gzip化?

voo*_*voo 51 java gzip http inputstream httpurlconnection

有没有办法检查InputStream是否已被gzip压缩?这是代码:

public static InputStream decompressStream(InputStream input) {
    try {
        GZIPInputStream gs = new GZIPInputStream(input);
        return gs;
    } catch (IOException e) {
        logger.info("Input stream not in the GZIP format, using standard format");
        return input;
    }
}
Run Code Online (Sandbox Code Playgroud)

我试过这种方式,但它没有按预期工作 - 从流中读取的值无效.编辑:添加了我用来压缩数据的方法:

public static byte[] compress(byte[] content) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
        GZIPOutputStream gs = new GZIPOutputStream(baos);
        gs.write(content);
        gs.close();
    } catch (IOException e) {
        logger.error("Fatal error occured while compressing data");
        throw new RuntimeException(e);
    }
    double ratio = (1.0f * content.length / baos.size());
    if (ratio > 1) {
        logger.info("Compression ratio equals " + ratio);
        return baos.toByteArray();
    }
    logger.info("Compression not needed");
    return content;

}
Run Code Online (Sandbox Code Playgroud)

biz*_*lop 64

它并非万无一失,但它可能是最简单的,不依赖于任何外部数据.像所有体面的格式一样,GZip也以一个神奇的数字开头,可以在不读取整个流的情况下快速检查.

public static InputStream decompressStream(InputStream input) {
     PushbackInputStream pb = new PushbackInputStream( input, 2 ); //we need a pushbackstream to look ahead
     byte [] signature = new byte[2];
     int len = pb.read( signature ); //read the signature
     pb.unread( signature, 0, len ); //push back the signature to the stream
     if( signature[ 0 ] == (byte) 0x1f && signature[ 1 ] == (byte) 0x8b ) //check if matches standard gzip magic number
       return new GZIPInputStream( pb );
     else 
       return pb;
}
Run Code Online (Sandbox Code Playgroud)

(幻数的来源:GZip文件格式规范)

更新:我刚刚dicovered这里面还有一个叫常GZIP_MAGICGZipInputStream其中包含这个值,所以如果你真的想,你可以使用它的两个低字节.

  • 好的方法,但是当流为空或只有一个字节时有一个错误.您需要检查读取的字节数,并仅回写那些读取的字节数.只有在成功读取两个字节时才应进行签名检查. (4认同)
  • 我相信你需要为PushBackInputStream使用2-arg构造函数,因为默认情况下它只允许你向后推1个字节(并且pb.unread(signature)推回2个字节).例如`new pushBackInputStream(input,2)` (2认同)

Bal*_*usC 40

InputStream来自HttpURLConnection#getInputStream()

在这种情况下,您需要检查HTTP Content-Encoding响应标头是否等于gzip.

URLConnection connection = url.openConnection();
InputStream input = connection.getInputStream();

if ("gzip".equals(connection.getContentEncoding())) {
    input = new GZIPInputStream(input);
}

// ...
Run Code Online (Sandbox Code Playgroud)

这一切都在HTTP规范中明确规定.


更新:按照压缩源流的方式:这个比率检查非常......疯狂.摆脱它.相同的长度并不一定意味着字节是相同的.让它总是返回gzip流,这样你就可以一直期望一个gzip流,只需应用GZIPInputStream而不需要讨厌的检查.


Aar*_*ler 24

我发现这个有用的例子提供了一个干净的实现isCompressed():

/*
 * Determines if a byte array is compressed. The java.util.zip GZip
 * implementaiton does not expose the GZip header so it is difficult to determine
 * if a string is compressed.
 * 
 * @param bytes an array of bytes
 * @return true if the array is compressed or false otherwise
 * @throws java.io.IOException if the byte array couldn't be read
 */
 public boolean isCompressed(byte[] bytes) throws IOException
 {
      if ((bytes == null) || (bytes.length < 2))
      {
           return false;
      }
      else
      {
            return ((bytes[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && (bytes[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8)));
      }
 }
Run Code Online (Sandbox Code Playgroud)

我成功测试了它:

@Test
public void testIsCompressed() {
    assertFalse(util.isCompressed(originalBytes));
    assertTrue(util.isCompressed(compressed));
}
Run Code Online (Sandbox Code Playgroud)


小智 8

我相信这是检查字节数组是否是gzip格式化的最简单方法,它不依赖于任何HTTP实体或mime类型支持

public static boolean isGzipStream(byte[] bytes) {
      int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
      return (GZIPInputStream.GZIP_MAGIC == head);
}
Run Code Online (Sandbox Code Playgroud)


blu*_*lue 5

基于@biziclop 的答案 - 该版本使用 GZIP_MAGIC 标头,并且对于空或单字节数据流也是安全的。

public static InputStream maybeDecompress(InputStream input) {
    final PushbackInputStream pb = new PushbackInputStream(input, 2);

    int header = pb.read();
    if(header == -1) {
        return pb;
    }

    int b = pb.read();
    if(b == -1) {
        pb.unread(header);
        return pb;
    }

    pb.unread(new byte[]{(byte)header, (byte)b});

    header = (b << 8) | header;

    if(header == GZIPInputStream.GZIP_MAGIC) {
        return new GZIPInputStream(pb);
    } else {
        return pb;
    }
}
Run Code Online (Sandbox Code Playgroud)


Ami*_*ani 1

将原始流包装在 BufferedInputStream 中,然后将其包装在 GZipInputStream 中。接下来尝试提取 ZipEntry。如果有效,则它是一个 zip 文件。然后,您可以在检查后在 BufferedInputStream 中使用“mark”和“reset”返回到流中的初始位置。