1. 概述

GZIP 格式是一种用于数据压缩的文件格式。Java 语言中的 GZipInputStreamGZipOutputStream 类实现了这种文件格式。

在这个教程中,我们将学习如何在 Java 中使用 GZIP 压缩数据,并查看如何将压缩后的数据写入字节数组。

2. GZipOutputStream

GZipOutputStream 类负责压缩并写入底层输出流的数据。

2.1. 对象实例化

我们可以使用构造函数创建此类的对象

ByteArrayOutputStream os = new ByteArrayOutputStream();
GZIPOutputStream gzipOs = new GZIPOutputStream(os);

在这里,我们向构造函数传递一个 ByteArrayOutputStream 对象。这样,之后我们可以通过调用 toByteArray() 方法获取压缩后的数据作为字节数组。

除了 ByteArrayOutputStream,我们还可以提供其他 OutputStream 的实例,例如:

  • FileOutputStream:将数据存储在文件中
  • ServletOutputStream:通过网络传输数据

在两种情况下,数据都会随着到来而发送到其目的地。

2.2. 压缩数据

write() 方法执行数据压缩

byte[] buffer = "Sample Text".getBytes();
gzipOs.write(buffer, 0, buffer.length);

write() 方法会压缩 buffer 字节数组的内容,并将其写入包装的输出流。

**除了 buffer 字节数组,write() 还包括两个额外参数 offsetlength**。它们定义了字节数组内部的一段范围。因此,我们可以使用这些参数指定要写入的字节范围,而不是整个 buffer

最后,为了完成数据压缩,我们需要调用 close()

gzipOs.close();

close() 方法会写入所有剩余数据并关闭流。因此,调用 close() 很重要,否则我们将丢失数据。

3. 获取压缩后的字节数组

我们将创建一个用于 GZIP 压缩数据的实用方法,同时也会看到如何获取包含压缩数据的字节数组。

3.1. 压缩数据

让我们创建一个名为 gzip() 的方法,用于以 GZIP 格式压缩数据

private static final int BUFFER_SIZE = 512;

public static void gzip(InputStream is, OutputStream os) throws IOException {
    GZIPOutputStream gzipOs = new GZIPOutputStream(os);
    byte[] buffer = new byte[BUFFER_SIZE];
    int bytesRead = 0;
    while ((bytesRead = is.read(buffer)) > -1) {
        gzipOs.write(buffer, 0, bytesRead);
    }
    gzipOs.close();
}

在这个方法中,首先我们创建一个新的 GZIPOutputStream 实例。然后,我们开始从 is 输入流中复制数据,使用 buffer 字节数组。

值得注意的是,我们会一直读取字节,直到返回值为 -1。**当到达流的末尾时,read() 方法会返回 -1**。

3.2. 获取包含压缩数据的字节数组

我们将压缩一个字符串并将结果写入字节数组。我们将使用之前创建的 gzip() 方法:

String payload = "This is a sample text to test the gzip method. Have a nice day!";
ByteArrayOutputStream os = new ByteArrayOutputStream();
gzip(new ByteArrayInputStream(payload.getBytes()), os);
byte[] compressed = os.toByteArray();

在这里,我们向 gzip() 方法提供输入和输出流。我们将 payload 值封装在一个 ByteArrayInputStream 对象中。然后,我们创建一个空的 ByteArrayOutputStreamgzip() 将在其中写入压缩数据。

最后,调用 gzip() 之后,我们可以使用 toByteArray() 方法获取压缩后的数据。

4. 测试

在测试我们的代码之前,让我们将 gzip() 方法添加到 GZip 类中。现在,我们准备好使用单元测试来测试我们的代码

@Test
void whenCompressingUsingGZip_thenGetCompressedByteArray() throws IOException {
    String payload = "This is a sample text to test method gzip. The gzip algorithm will compress this string. "
        + "The result will be smaller than this string.";
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    GZip.gzip(new ByteArrayInputStream(payload.getBytes()), os);
    byte[] compressed = os.toByteArray();
    assertTrue(payload.getBytes().length > compressed.length);
    assertEquals("1f", Integer.toHexString(compressed[0] & 0xFF));
    assertEquals("8b", Integer.toHexString(compressed[1] & 0xFF));
}

在这个测试中,我们压缩一个字符串值。我们将字符串转换为 ByteArrayInputStream 并将其提供给 gzip() 方法。此外,输出数据会写入 ByteArrayOutputStream

如果满足以下两个条件,那么测试就成功了:

  1. 压缩后的数据大小小于未压缩的
  2. 压缩后的字节数组以值 1f 8b 开始。

关于第二个条件,GZIP 文件以固定的值 1f 8b 开头,以符合 GZIP 文件格式

因此,如果我们运行单元测试,我们将验证这两个条件都为真。

5. 总结

在这篇文章中,我们学习了如何在 Java 中使用 GZIP 文件格式获取压缩后的字节数组。为此,我们创建了一个压缩实用方法。最后,我们测试了我们的代码。

如往常一样,我们示例的完整源代码可以在 GitHub 上找到。