微前端架构如何改变企业的开发模式与效率提升
537
2022-10-25
GZIPOutputStream 类源码分析
这是《水煮 JDK 源码》系列 的第5篇文章,计划撰写100篇关于JDK源码相关的文章
GZIPOutputStream 类位于 java.util.zip 包下,继承于 DeflaterOutputStream 类,它实现了一个流式过滤器,主要用于以GZIP文件格式写入压缩数据,其UML类图如下:
::: hljs-center
:::
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(); } } }
运行程序后,由于是压缩后的字符串,所以输出打印的是乱码。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~