Статьи

Работа с GZIP и сжатыми данными

Аннотация

Мы все знаем, что значит архивировать файл с помощью zip или gzip. Но использовать сжатые файлы в Java не так просто, как хотелось бы думать, особенно если вы работаете не с файлами напрямую, а с компрессией потоковых данных. Мы пойдем, хотя:

  • как преобразовать строку в сжатый / сжатый байтовый массив и наоборот
  • создавать служебные функции для чтения и записи файлов без необходимости заранее знать, является ли файл или поток gzip или нет.

Основы

Так почему вы хотите что-то застегнуть? Просто потому, что это отличный способ сократить объем данных, которые вам нужно передавать по сети или сохранить на диск, и, следовательно, увеличить скорость выполнения операции. Типичный текстовый файл или сообщение может быть уменьшено в 10 и более раз в зависимости от характера вашего документа. Конечно, вам придется учитывать стоимость архивирования и разархивирования, но когда у вас большой объем данных, маловероятно, что эти затраты будут значительными.

Java поддерживает это?

Да, Java поддерживает чтение и запись файлов gzip в пакете java.util.zip . Он также поддерживает zip-файлы, а также раздувание и выкачивание данных из популярной библиотеки сжатия ZLIB.

Как мне сжать / распаковать строку Java?

Вот пример того, как сжимать и распаковывать строку, используя DeflaterOutputStream.

Вот два метода использования встроенного в Java компрессора, а также метод, использующий GZIP:

  1. Использование DeflaterOutputStream — самый простой способ:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    enum StringCompressor {
            ;
            public static byte[] compress(String text) {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try {
                    OutputStream out = new DeflaterOutputStream(baos);
                    out.write(text.getBytes("UTF-8"));
                    out.close();
                } catch (IOException e) {
                    throw new AssertionError(e);
                }
                return baos.toByteArray();
            }
     
            public static String decompress(byte[] bytes) {
                InputStream in = new InflaterInputStream(new ByteArrayInputStream(bytes));
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try {
                    byte[] buffer = new byte[8192];
                    int len;
                    while((len = in.read(buffer))>0)
                        baos.write(buffer, 0, len);
                    return new String(baos.toByteArray(), "UTF-8");
                } catch (IOException e) {
                    throw new AssertionError(e);
                }
            }
        }
  2. Если вы хотите использовать Deflater / Inflater напрямую:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    enum StringCompressor2 {
            ;
            public static byte[] compress(String text) throws Exception{
                byte[] output = new byte;
                Deflater compresser = new Deflater();
                compresser.setInput(text.getBytes("UTF-8"));
                compresser.finish();
                int compressedDataLength = compresser.deflate(output);
                byte[] dest = new byte[compressedDataLength];
                System.arraycopy(output, 0, dest, 0, compressedDataLength);
                return dest;
            }
     
            public static String decompress(byte[] bytes) throws Exception{
                Inflater decompresser = new Inflater();
                decompresser.setInput(bytes, 0, bytes.length);
                byte[] result = new byte[bytes.length *10];
                int resultLength = decompresser.inflate(result);
                decompresser.end();
     
                // Decode the bytes into a String
                String outputString = new String(result, 0, resultLength, "UTF-8");
                return outputString;
            }
        }
  3. Вот как это сделать с помощью GZIP:
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    enum StringGZipper {
            ;
            private static String ungzip(byte[] bytes) throws Exception{
                InputStreamReader isr = new InputStreamReader(new GZIPInputStream(new ByteArrayInputStream(bytes)), StandardCharsets.UTF_8);
                StringWriter sw = new StringWriter();
                char[] chars = new char[1024];
                for (int len; (len = isr.read(chars)) > 0; ) {
                    sw.write(chars, 0, len);
                }
                return sw.toString();
            }
     
            private static byte[] gzip(String s) throws Exception{
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                GZIPOutputStream gzip = new GZIPOutputStream(bos);
                OutputStreamWriter osw = new OutputStreamWriter(gzip, StandardCharsets.UTF_8);
                osw.write(s);
                osw.close();
                return bos.toByteArray();
            }
        }

Как декодировать поток байтов, чтобы разрешить как GZip, так и обычные потоки:

Приведенный ниже код превратит поток байтов в строку (дамп) независимо от того, нужно ли заранее знать, был ли этот поток сжатым или нет.

01
02
03
04
05
06
07
08
09
10
11
12
if (isGZIPStream(bytes)) {
            InputStreamReader isr = new InputStreamReader(new GZIPInputStream(new ByteArrayInputStream(bytes)), StandardCharsets.UTF_8);
            StringWriter sw = new StringWriter();
            char[] chars = new char[1024];
            for (int len; (len = isr.read(chars)) > 0; ) {
                sw.write(chars, 0, len);
            }
            dump = sw.toString();
        } else {
            dump = new String(bytes, 0, length, StandardCharsets.UTF_8);
        }
}

Это реализация метода isGZIPStream. Раскрывает правду о том, что стоит за GZIP_MAGIC!

1
2
3
4
public static boolean isGZIPStream(byte[] bytes) {
        return bytes[0] == (byte) GZIPInputStream.GZIP_MAGIC
         && bytes[1] == (byte) (GZIPInputStream.GZIP_MAGIC >>> 8);
}

Это простой способ прочитать файл, не зная, был ли он заархивирован или нет (полагаясь на расширение .gz).

01
02
03
04
05
06
07
08
09
10
11
12
13
static Stream<String> getStream(String dir, @NotNull String fileName)
  throws IOException {
        File file = new File(dir, fileName);
        InputStream in;
        if (file.exists()) {
            in = new FileInputStream(file);
        } else {
            file = new File(dir, fileName + ".gz");
            in = new GZIPInputStream(new FileInputStream(file));
        }
 
        return new BufferedReader(new InputStreamReader(in)).lines();
}
Ссылка: Работа с GZIP и сжатыми данными нашего партнера по JCG Даниэля Шая в блоге Rational Java .