GZIPInputStream 类源码分析

网友投稿 514 2022-10-25

GZIPInputStream 类源码分析

GZIPInputStream 类源码分析

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

GZIPInputStream 类位于 java.util.zip 包下,继承于 InflaterInputStream 类,它实现了一个流式过滤器,主要用于读取GZIP文件格式的压缩数据,其UML类图如下:

::: hljs-center

:::

public class GZIPInputStream extends InflaterInputStream

1、成员变量

GZIPInputStream 定义了3个成员变量,分别如下:

/** CRC-32 用于未压缩的数据 */ protected CRC32 crc = new CRC32(); /** 表示输入流的结尾状态 */ protected boolean eos; /** 输入流是否已关闭的状态 */ private boolean closed = false;

2、构造函数

创建 GZIPInputStream 压缩输入流主要有以下的两种方式:

/** 使用默认大小的缓冲区创建新的输入流 */ public GZIPInputStream(InputStream in) throws IOException { // 默认缓冲区大小为512 this(in, 512); } /** 使用指定大小的缓冲区创建新的输入流 */ public GZIPInputStream(InputStream in, int size) throws IOException { // 调用父类 InflaterInputStream 的构造函数 super(in, new Inflater(true), size); // 设置父类 InflaterInputStream 的 usesDefaultInflater // 表示使用默认的解压缩器 usesDefaultInflater = true; // 读取 GZIP 的成员头信息,并返回头信息的总字节数 readHeader(in); }

由于 GZIPInputStream 是由于读取压缩数据的输入流,因此需要用到解压缩器 Inflater。

3、读取数据方法

GZIPInputStream 主要提供了1个用于读取流数据的方法,如下:

public int read(byte[] buf, int off, int len) throws IOException { // 在正式读取流数据之前,要确保流没有被关闭 ensureOpen(); // 如果已经到了流的结尾,说明没有可读的数据了,直接返回 -1 if (eos) { return -1; } // 调用父类 InflaterInputStream 的 read() 方法读取数据 int n = super.read(buf, off, len); // 如果实际读取到的数据为 -1,说明没有可读数据 if (n == -1) { // 读取 GZIP 的成员尾部信息,判断是否读取到了 eos // 如果是,则将 eos 置为 true,表示已读取到了尾部 if (readTrailer()) eos = true; else // 否则,继续调用本方法进行读取 return this.read(buf, off, len); } else { // 如果读取了数据,则使用指定的字节数据更新 CRC32 的校验和 crc.update(buf, off, n); } // 返回实际读取的字节数 return n; }

在读取数据之前,需要先检查流是否被关闭,如果流已经被关闭了,说明是不可读的,ensureOpen() 方法就是作此用途,其定义如下:

private void ensureOpen() throws IOException { if (closed) { // 如果流被关闭了,直接抛出 IOException 异常 throw new IOException("Stream closed"); } }

在创建 GZIPInputStream 输入流的时候,需要去读取 GZIP 的成员头信息,readHeader() 方法定义如下:

/** GZIP 头魔法数 */ public final static int GZIP_MAGIC = 0x8b1f; /** 文件头标识 */ private final static int FTEXT = 1; // Extra text private final static int FHCRC = 2; // Header CRC private final static int FEXTRA = 4; // Extra field private final static int FNAME = 8; // File name private final static int FCOMMENT = 16; // File comment private int readHeader(InputStream this_in) throws IOException { // 创建 CheckedInputStream,用于维护数据的校验和 CheckedInputStream in = new CheckedInputStream(this_in, crc); // 重置 CRC32 校验 crc.reset(); // 检查头部魔法数,判断是否为 GZIP 格式 if (readUShort(in) != GZIP_MAGIC) { throw new ZipException("Not in GZIP format"); } // 检查压缩方法,判断是否为支持的压缩方法 if (readUByte(in) != 8) { throw new ZipException("Unsupported compression method"); } // 读取标识 int flg = readUByte(in); // Skip MTIME, XFL, and OS fields // 跳过特殊的字段 skipBytes(in, 6); int n = 2 + 2 + 6; // Skip optional extra field if ((flg & FEXTRA) == FEXTRA) { int m = readUShort(in); skipBytes(in, m); n += m + 2; } // Skip optional file name if ((flg & FNAME) == FNAME) { do { n++; } while (readUByte(in) != 0); } // Skip optional file comment if ((flg & FCOMMENT) == FCOMMENT) { do { n++; } while (readUByte(in) != 0); } // Check optional header CRC if ((flg & FHCRC) == FHCRC) { int v = (int)crc.getValue() & 0xffff; if (readUShort(in) != v) { throw new ZipException("Corrupt GZIP header"); } n += 2; } // 重置 CRC32 校验 crc.reset(); // 返回头信息的总字节数 return n; }

在读取数据的时候,需要读取GZIP的尾部信息,并以此来判断是否已读取结束了,readTrailer() 方法如下:

private boolean readTrailer() throws IOException { InputStream in = this.in; // 获取余下可读的总字节长度,调用的是 Inflater 的 getRemaining 方法 int n = inf.getRemaining(); // 如果可读字节数大于0 if (n > 0) { // 创建序列输入流 in = new SequenceInputStream( new ByteArrayInputStream(buf, len - n, n), new FilterInputStream(in) { public void close() throws IOException {} }); } // Uses left-to-right evaluation order if ((readUInt(in) != crc.getValue()) || // rfc1952; ISIZE is the input size modulo 2^32 (readUInt(in) != (inf.getBytesWritten() & 0xffffffffL))) throw new ZipException("Corrupt GZIP trailer"); // If there are more bytes available in "in" or // the leftover in the "inf" is > 26 bytes: // this.trailer(8) + next.header.min(10) + next.trailer(8) // try concatenated case if (this.in.available() > 0 || n > 26) { int m = 8; // this.trailer try { m += readHeader(in); // next.header } catch (IOException ze) { return true; // ignore any malformed, do nothing } inf.reset(); if (n > m) inf.setInput(buf, len - n + m, n - m); return false; } return true; }

不论是在读取头信息或者尾信息的时候,都会去读取指定的标识位长度,比如 readUInt、readUShort、readUByte 方法,分别用于读取无符号整型、无符号短整型、无符号字节数据,其定义如下:

private long readUInt(InputStream in) throws IOException { long s = readUShort(in); return ((long)readUShort(in) << 16) | s; } private int readUShort(InputStream in) throws IOException { int b = readUByte(in); return (readUByte(in) << 8) | b; } private int readUByte(InputStream in) throws IOException { // 读取字节数据,获取所读取的总字节数 int b = in.read(); // 如果总字节数为 -1,说明已经读完了 if (b == -1) { throw new EOFException(); } if (b < -1 || b > 255) { // Report on this.in, not argument in; see read{Header, Trailer}. throw new IOException(this.in.getClass().getName() + ".read() returned value out of range -1..255: " + b); } return b; }

4、其他方法

在读取压缩数据流的时候,也可以跳过指定的字节数,其方法定义如下:

private byte[] tmpbuf = new byte[128]; /** 跳过输入流中指定长度的字节数据,该方法是阻塞的,直到所有字节都跳过 */ private void skipBytes(InputStream in, int n) throws IOException { while (n > 0) { int len = in.read(tmpbuf, 0, n < tmpbuf.length ? n : tmpbuf.length); if (len == -1) { throw new EOFException(); } n -= len; } }

GZIPInputStream 的 close() 方法如下:

public void close() throws IOException { // 首先判断输入流是否已关闭 if (!closed) { // 如果没有关闭,调用父类的 close 方法对输入流进行关闭 super.close(); // 同时设置 eos 为 ture,标识该输入流已结束 eos = true; // 修改已关闭的状态 closed = true; } }

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Metaflow- 现实生活中的数据科学框架
下一篇:基于element-ui框架的一个简洁后台
相关文章

 发表评论

暂时没有评论,来抢沙发吧~