Как проверить, является ли InputStream Gzipped? - PullRequest
48 голосов
/ 27 января 2011

Есть ли способ проверить, был ли InputStream разархивирован? Вот код:

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;
    }
}

Я пробовал этот способ, но он не работает должным образом - значения, считанные из потока, недопустимы. РЕДАКТИРОВАТЬ: Добавлен метод, который я использую для сжатия данных:

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;

}

Ответы [ 10 ]

62 голосов
/ 27 января 2011

Это не надежно, но это, вероятно, самый простой и не зависит от каких-либо внешних данных.Как и все приличные форматы, 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;
}

(Источник для магического числа: Спецификация формата файла GZip )

Обновление: Я только что обнаружил, что есть такжеконстанта с именем GZIP_MAGIC в GZipInputStream, которая содержит это значение, поэтому, если вы действительно хотите 1011 , вы можете использовать ее младшие два байта.

39 голосов
/ 27 января 2011

InputStream исходит из HttpURLConnection # getInputStream ()

В этом случае вам необходимо проверить, равен ли заголовок ответа HTTP Content-Encoding gzip.

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

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

// ...

Это все четко указано в HTTP spec .


Обновление : в зависимости от способа сжатия источника потока: это соотношениепроверка довольно ... безумная.Избавиться от этого.Одна и та же длина не обязательно означает, что байты одинаковы.Пусть он всегда возвращает поток gzipped, так что вы можете всегда ожидать поток gzipped и просто применить GZIPInputStream без неприятных проверок.

21 голосов
/ 24 декабря 2011

Я нашел полезный пример , который обеспечивает чистую реализацию 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)));
      }
 }

Я успешно его протестировал:

@Test
public void testIsCompressed() {
    assertFalse(util.isCompressed(originalBytes));
    assertTrue(util.isCompressed(compressed));
}
8 голосов
/ 11 марта 2011

Я считаю, что это самый простой способ проверить, отформатирован ли байтовый массив в формате gzip или нет, он не зависит от какой-либо сущности HTTP или поддержки типа mime

public static boolean isGzipStream(byte[] bytes) {
      int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
      return (GZIPInputStream.GZIP_MAGIC == head);
}
3 голосов
/ 28 декабря 2016

Опираясь на ответ @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;
    }
}
2 голосов
/ 22 августа 2016

Эта функция прекрасно работает в Java :

public static boolean isGZipped(File f) {   
    val raf = new RandomAccessFile(file, "r")
    return GZIPInputStream.GZIP_MAGIC == (raf.read() & 0xff | ((raf.read() << 8) & 0xff00))
}

In scala :

def isGZip(file:File): Boolean = {
   int gzip = 0
   RandomAccessFile raf = new RandomAccessFile(f, "r")
   gzip = raf.read() & 0xff | ((raf.read() << 8) & 0xff00)
   raf.close()
   return gzip == GZIPInputStream.GZIP_MAGIC
}
1 голос
/ 27 января 2011

Не совсем то, что вы просите, но может быть альтернативным подходом, если вы используете HttpClient:

private static InputStream getInputStream(HttpEntity entity) throws IOException {
  Header encoding = entity.getContentEncoding(); 
  if (encoding != null) {
     if (encoding.getValue().equals("gzip") || encoding.getValue().equals("zip") ||      encoding.getValue().equals("application/x-gzip-compressed")) {
        return new GZIPInputStream(entity.getContent());
     }
  }
  return entity.getContent();
}
1 голос
/ 27 января 2011

Оберните исходный поток в BufferedInputStream, затем оберните его в GZipInputStream.Затем попробуйте извлечь ZipEntry.Если это работает, это zip-файл.Затем вы можете использовать «mark» и «reset» в BufferedInputStream, чтобы вернуться к начальной позиции в потоке после проверки.

0 голосов
/ 28 сентября 2016

SimpleMagic - это библиотека Java для разрешения типов контента:

<!-- pom.xml -->
    <dependency>
        <groupId>com.j256.simplemagic</groupId>
        <artifactId>simplemagic</artifactId>
        <version>1.8</version>
    </dependency>

import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.j256.simplemagic.ContentType;
// ...

public class SimpleMagicSmokeTest {

    private final static Logger log = LoggerFactory.getLogger(SimpleMagicSmokeTest.class);

    @Test
    public void smokeTestSimpleMagic() throws IOException {
        ContentInfoUtil util = new ContentInfoUtil();
        InputStream possibleGzipInputStream = getGzipInputStream();
        ContentInfo info = util.findMatch(possibleGzipInputStream);

        log.info( info.toString() );
        assertEquals( ContentType.GZIP, info.getContentType() );
    }
0 голосов
/ 03 ноября 2015

Как прочитать файл, который МОЖЕТ БЫТЬ разархивирован:

private void read(final File file)
        throws IOException {
    InputStream stream = null;
    try (final InputStream inputStream = new FileInputStream(file);
            final BufferedInputStream bInputStream = new BufferedInputStream(inputStream);) {
        bInputStream.mark(1024);
        try {
            stream = new GZIPInputStream(bInputStream);
        } catch (final ZipException e) {
            // not gzipped OR not supported zip format
            bInputStream.reset();
            stream = bInputStream;
        }
        // USE STREAM HERE
    } finally {
        if (stream != null) {
            stream.close();
        }
    }
}
...