GZIPInputStream 类源码分析
Posted 然笑后端
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GZIPInputStream 类源码分析相关的知识,希望对你有一定的参考价值。
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 readHeader, 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;
以上是关于GZIPInputStream 类源码分析的主要内容,如果未能解决你的问题,请参考以下文章