Java 8 中 GZIPOutputStream类源码介绍

这是《水煮 JDK 源码》系列 的第5篇文章,计划撰写100篇关于JDK源码相关的文章

GZIPOutputStream 类位于 java.util.zip 包下,继承于 DeflaterOutputStream 类,它实现了一个流式过滤器,主要用于以GZIP文件格式写入压缩数据,其UML类图如下:
在这里插入图片描述
类声明如下:

public class GZIPOutputStream extends DeflaterOutputStream

1、成员变量

GZIPOutputStream 定义了1个成员变量,如下:

/** CRC-32 用于未压缩的数据 */
protected CRC32 crc = new CRC32();

CRC32 是一个计算数据流的 CRC-32 校验和的类,主要用来验证压缩数据的完整性的。

2、构造函数

创建 GZIPOutputStream 压缩输出流主要有4种方式,如下:

public GZIPOutputStream(OutputStream out) throws IOException {
    /** 使用默认大小的缓冲区创建新的输出流,默认大小为 512,同时不进行同步刷新 */
    this(out, 512, false);
}

public GZIPOutputStream(OutputStream out, boolean syncFlush)
    throws IOException
{
     /** 使用默认大小的缓冲区创建新的输出流,默认大小为 512 */
    this(out, 512, syncFlush);
}

public GZIPOutputStream(OutputStream out, int size) throws IOException {
    /** 使用指定大小的缓冲区创建新的输出流,不进行同步刷新 */
    this(out, size, false);
}

public GZIPOutputStream(OutputStream out, int size, boolean syncFlush)
    throws IOException
{
    /** 使用指定大小的缓冲区创建新的输出流 */
    super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true),
          size,
          syncFlush);
    // 使用默认的压缩器
    usesDefaultDeflater = true;
    // 写入 GZIP 成员头信息
    writeHeader();
    // 重置 CRC-32 校验
    crc.reset();
}

GZIPOutputStream 类主要是用于写入GZIP压缩数据的输出流,因此需要用到压缩器 Deflater

3、写入数据方法

GZIPOutputStream 类提供了一个写入数据的方法,其定义如下:

public synchronized void write(byte[] buf, int off, int len)
    throws IOException
{
    // 调用父类 DeflaterOutputStream 的 write() 方法写入字节数组数据
    super.write(buf, off, len);
    // 更新字节数组数据的 CRC32 校验和
    crc.update(buf, off, len);
}

在创建 GZIPOutputStream 压缩输出流的时候,会使用 writeHeader() 方法写入GZIP成员头信息,那么具体会写入哪些头信息呢?可以看看该方法的定义:

private void writeHeader() throws IOException {
    // 这个 out 是定义在父类 FilterOutputStream 中的成员变量
    out.write(new byte[] {
        (byte) GZIP_MAGIC,        // Magic number (short)
        (byte)(GZIP_MAGIC >> 8),  // Magic number (short)
        Deflater.DEFLATED,        // Compression method (CM)
        0,                        // Flags (FLG)
        0,                        // Modification time MTIME (int)
        0,                        // Modification time MTIME (int)
        0,                        // Modification time MTIME (int)
        0,                        // Modification time MTIME (int)
        0,                        // Extra flags (XFLG)
        0                         // Operating system (OS)
    });
}

从代码实现可以看出,一共会写入10个字节的头信息,包括头信息魔法数、压缩方法和各种标志等,这些头信息在解压缩的时候也是需要一一校验的。

有写头信息的方法,自然就会有写尾信息的方法,关于写尾信息的 writeTrailer() 方法定义如下:

private void writeTrailer(byte[] buf, int offset) throws IOException {
    // 写入 CRC32 的 value 值,int 型数据占用 4个字节
    writeInt((int)crc.getValue(), buf, offset); // CRC-32 of uncompr. data
    // 写入未压缩的字节总数
    writeInt(def.getTotalIn(), buf, offset + 4); // Number of uncompr. bytes
}

上面调用的 writeInt() 方法定义如下:

private void writeInt(int i, byte[] buf, int offset) throws IOException {
    // 在写入 int 数据时,是写入两次 short 型数据
    writeShort(i & 0xffff, buf, offset);
    writeShort((i >> 16) & 0xffff, buf, offset + 2);
}

private void writeShort(int s, byte[] buf, int offset) throws IOException {
    // 而写入 short 数据时,时写入两次 byte 型数据
    buf[offset] = (byte)(s & 0xff);
    buf[offset + 1] = (byte)((s >> 8) & 0xff);
}

4、其他方法

当压缩的数据都写入到输出流中时,这个时候可以调用完成 finish() 方法,如下:

public void finish() throws IOException {
    // 判断压缩数据是否完成,这个是调用压缩器 Deflater 的 finished() 方法
    // 如果没有完成,则调用 Deflater 的 finish() 完成方法
    if (!def.finished()) {
        def.finish();
        while (!def.finished()) {
            // 压缩 buf 数据
            int len = def.deflate(buf, 0, buf.length);
            // 如果 def 已经完成,但是压缩后的数据长度小于减去尾部信息的长度
            // 则写入尾部信息
            if (def.finished() && len <= buf.length - TRAILER_SIZE) {
                // last deflater buffer. Fit trailer at the end
                writeTrailer(buf, len);
                len = len + TRAILER_SIZE;
                out.write(buf, 0, len);
                return;
            }
            // 如果压缩后的数据长度大于 0,直接写入到输出流中
            if (len > 0)
                out.write(buf, 0, len);
        }
        // if we can't fit the trailer at the end of the last
        // deflater buffer, we write it separately
        // 单独的写入尾部信息,尾部信息的长度是 8个字节
        byte[] trailer = new byte[TRAILER_SIZE];
        writeTrailer(trailer, 0);
        // 将尾部信息写入到
        out.write(trailer);
    }
}

5、测试应用

GZIPOutputStream 类可以用来压缩数据,那么下面给出一个压缩字符串的示例代码:

package com.magic.test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;

public class GZIPOutputStreamTest {

    public static void main(String[] args) {
        String str = "0123456";
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();
             GZIPOutputStream gzip = new GZIPOutputStream(out)) {

            // 将字符串 str 转换为字节数组后写入到 gzip 压缩输出流中
            gzip.write(str.getBytes());

            // 打印压缩后的数据
            System.out.println(out.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行程序后,由于是压缩后的字符串,所以输出打印的是乱码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值