java.util.zip 包,包含了6个流类和其他6个其他各式各样用于读写zip,gzip和其他解压缩格式。
Java使用这些类来读取写入JAR文档和显示PNG图片。
你可以将这些工具用于通用的压缩和解压缩过程。
由于有了这些类,压缩和解压缩变得很容易。
2 Inflaters and Deflaters (解压者和压缩者)
java.util.zip.Deflater 和 java.util.zip.Inflater 为其他类提供压缩和解压的能力。
它们是Java的压缩和解压的引擎
大部分都是CPU密集型计算,所以底层使用了C来写.
3 压缩数据
Deflater类包含了压缩块数据的方法。
你可以选择压缩格式,级别和策略。
Deflater9步完成压缩:
1. 构造Deflater对象。
2. 选择策略(可选)。
3. 设置压缩级别(可选)。
4. 重置字典(可选)。
5. 设置输入
6. 重复压缩数据,直到needsInput()返回true
7. 如果有更多的可用数据,返回第5步继续添加。
否则,进行第8步
8. 完成数据
9. 如果有更多的流需要压缩,重启压缩器。
4 构造解压对象
三个构造函数:
public Deflater(int level, boolean useGzip)
public Deflater(int level)
public Deflater( )
例如:
public static final int NO_COMPRESSION = 0;
public static final int BEST_SPEED = 1;
public static final int BEST_COMPRESSION = 9;
public static final int DEFAULT_COMPRESSION = -1;
如果useGzip为true,则使用gzip;否则,使用zlib格式。
5 策略选择
Java支持的压缩策略包括:filtered, Huffman, and default
public void setStrategy(int strategy)
支持:
Deflater.FILTERED = 1;
Deflater.HUFFMAN_ONLY = 2;
Deflater.DEFAULT_STRATEGY = -1;
默认策略:总是重复的词
哈弗曼编码:各个词频不一样
过滤器:可以妥协的2进制数据(例如音频视频之类)
6 设置压缩级别
除了在构造函数中进行级别的指定,你还可以通过如下方法:
public void setLevel(int Level)
来实现对压缩级别的指定。
总的而言,压缩速度和压缩质量总是负相关的
好的编码方式是使用下面几个常量中的一个:
Deflater.NO_COMPRESSION (0),
Deflater.BEST_SPEED (1),
Deflater.BEST_COMPRESSION (9),
Deflater.DEFAULT_COMPRESSION (-1)
而不是一个显式的值。
在小文件上做的测试,发现0~9直接差别不大。
对于GIF, JPEG, or PNG这类图片文件,他们具有内置的压缩方式,所以通用的压缩反而可能增加他们的体积。
7 设置压缩字典
默认的压缩字典是根据顺序来进行的,它将最先读到的内容放置到字典中。第二次读到这个词时,将它用对应字典的位置来替换。
你可以为你的压缩文件预设这样的文本值
额外值得说明的是:由于文件直接差别很大,而设置字典通常需要字典本身的存储空间,所以设计上一定要小心谨慎。
8 其他过程
byte[] originalBytes = ...
Deflater deflater = new Deflater();
deflater.setInput(originalBytes);
deflater.finish();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
while (!deflater.finished()) {
int byteCount = deflater.deflate(buf);
baos.write(buf, 0, byteCount);
}
deflater.end();
byte[] compressedBytes = baos.toByteArray();
如果你需要使用相同的策略,无需创建一个新的压缩器,而是使用压缩器的reset()方法
9 检查解压器的状态
Deflater提供了几个用于检查状态的方法
public int getAdler( )
提供了对未压缩内容的32位地址校验和。
public int getTotalIn( )
返回setInput中设置的字节数
public int getTotalOut( )
返回deflate方法返回的字节数
例如下面这个代码,说明了压缩了多少:
System.out.println((1.0 - def.getTotalOut()/def.getTotalIn( ))*100.0 +
"% saved");
解压器包含了用于解压zip, gzip, or zlib格式的方法。由于参数较少,使用比Deflater 要简单。
你需要做下面几部:
1 构件一个 Inflater对象,
2 设置输入经过压缩了的数据
3 调用needsDictionary( )判断是否需要预置字典
4 如果需要字典,则使用getAdler( ) 获得字典的32位地址校验和,然后调用setDictionary( )来设置字典
5 解压数据直到inflate( ) 返回0
6 如果needsInput( )返回真,返回第2步继续添加数据
7 使用finished()来返回true
如果需要使用Inflater 解压更多数据,重置它。
你通常很少使用这个类,考虑使用InflaterInputStream 或 InflaterOutputStream。
11 解压其他操作
可类比参考压缩流:
byte[] compressedBytes = ...
int decompressedByteCount = ... // From your format's metadata.
Inflater inflater = new Inflater();
inflater.setInput(compressedBytes, 0, compressedBytes.length);
byte[] decompressedBytes = new byte[decompressedByteCount];
if (inflater.inflate(decompressedBytes) != decompressedByteCount) {
throw new AssertionError();
}
inflater.end();
12 检查解压器的状态
public int getAdler( )获得32位校验和
public int getTotalIn( )返回 setInput( )设置的字节数
public int getTotalOut( )返回通过 inflate( )总数
public int getRemaining( )返回还剩下的压缩字节数
13 高级压缩流
总而言之,Deflater和Inflater有一些原始。所以我们推荐使用:
java.util.zip.DeflaterOutputStream 类是一个过滤流,它在将内容写到潜在的流之前会进行压缩。
java.util.zip.InflaterInputStream 类将在把内容传递到程序之前进行一个解压。
java.util.zip.GZIPInputStream 和java.util.zip.GZIPOutputStream 与上面做的事情相同,不过他是GZIP
14 压缩流使用例子
你就像使用别的过滤流一样使用就好
import java.io.*;
import java.util.zip.*;
public class GZipper {
public final static String GZIP_SUFFIX = ".gz";
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
try {
InputStream fin = new FileInputStream(args[i]);
OutputStream fout = new FileOutputStream(args[i] + GZIP_SUFFIX);
GZIPOutputStream gzout = new GZIPOutputStream(fout);
for (int c = fin.read(); c != -1; c = fin.read( )) {
gzout.write(c);
}
gzout.close( );
}
catch (IOException ex) {
System.err.println(ex);
}
}
}
}
15 Zip文件
Gzip仅仅是压缩方式,而zip则既是压缩方式,又是归档方式。
java.util.zip.ZipFile代表了整个文档。
java.util.zip.ZipEntry代表了在归档文件中的单独的一个文件。
ZipFile拥有多个构造方法
public ZipFile(String filename) throws ZipException, IOException
public ZipFile(File file) throws ZipException, IOException
public ZipFile(File file, int mode) throws IOException
ZipFile.READ 或 ZipFile.DELETE
设定为ZipFile.DELETE后,会自动删除文件。
你可以使用:
public Enumeration extends ZipEntry> entries( )
来获得ZipFile的所有条目。
获得一个ZipEntry:
public ZipEntry getEntry(String name)
获得对应ZipEntry的输入流:
public InputStream getInputStream(ZipEntry ze) throws IOException
16 Zip条目
使用构造方法来进行对象拷贝。
拷贝内容将会包含特定的属性,例如下面这些get方法对应的属性:
public String getName( )
public long getTime( )
public long getSize( )
public long getCompressedSize( )
public long getCrc( )
public int getMethod( )
public byte[] getExtra( )
public String getComment( )
public boolean isDirectory( )
Java可以使用两种格式保存zip格式。无压缩的或者压缩的。
分别表示为:
public static final int STORED = ZipEntry.STORED;
public static final int DEFLATED = ZipEntry.DEFLATED;
因为:
public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants
由于zip不仅是压缩形式也是归档形式,所以它包含了多个内容项,这些内容项各自包含了压缩和储存的文件。
此外,zip文件头部包含了归档本身的信息,例如条目的位置。
因此,不该在输出流中写入原始的压缩的数据。
因此必须创建连续的文件,然后写入到条目中。
下面几步:
Construct a ZipOutputStream object from an underlying stream, most often a file output stream.
Set the comment for the zip file (optional).
Set the default compression level and method (optional).
Construct a ZipEntry object.
Set the metainformation for the zip entry.
Put the zip entry in the archive.
Write the entry's data onto the output stream.
Close the zip entry (optional).
Repeat steps 4 through 8 for each entry you want to store in the archive.
Finish the zip output stream.
Close the zip output stream.
常规的write( ), flush( ), and close( )将导致错误。
详细解释:
1> 构造和初始化ZipOutputStream:
FileOutputStream fout = new FileOutputStream("data.zip");
ZipOutputStream zout = new ZipOutputStream(fout);
public void setComment(String comment)
设置方法:
public void setMethod(int method)
包括:
设置级别:
public void setLevel(int level)
0无压缩 9最高压缩
4> 构建ZipEntry并放入
public void putNextEntry(ZipEntry ze) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
6> 关闭Entry
public void closeEntry( ) throws IOException
public void finish( ) throws IOException
8> 关闭输出流
public void close( ) throws IOException
例子(截取):
FileOutputStream fout = new FileOutputStream(outputFile);
ZipOutputStream zout = new ZipOutputStream(fout);
zout.setLevel(level);
for (int i = start; i < args.length; i++) {
ZipEntry ze = new ZipEntry(args[i]);
FileInputStream fin = new FileInputStream(args[i]);
try {
System.out.println("Compressing " + args[i]);
zout.putNextEntry(ze);
for (int c = fin.read(); c != -1; c = fin.read( )) {
zout.write(c);
}
}
finally {
fin.close( );
}
}
zout.close( );
文件输入流读取字符,然后用zip输出流进行输出。
所以ZipEntry用于指定输出的压缩内容。
大概就是两步,指定nextEntry ;然后写入
18 The ZipInputStream Class
和输出流很相似,也有几步:
Construct a ZipInputStream object from an underlying stream.
Open the next zip entry in the archive.
Read data from the zip entry using InputStream methods such as read( ).
Close the zip entry (optional).
Repeat steps 2 through 4 as long as there are more entries (files) remaining in the archive.
Close the zip input stream.
样例:
import java.util.zip.*;
import java.io.*;
public class Unzipper2 {
public static void main(String[] args) throws IOException {
for (int i = 0; i < args.length; i++) {
FileInputStream fin = new FileInputStream(args[i]);
ZipInputStream zin = new ZipInputStream(fin);
ZipEntry ze = null;
while ((ze = zin.getNextEntry( )) != null) {
System.out.println("Unzipping " + ze.getName( ));
FileOutputStream fout = new FileOutputStream(ze.getName( ));
for (int c = zin.read(); c != -1; c = zin.read( )) {
fout.write(c);
}
zin.closeEntry( );
fout.close( );
}
zin.close( );
}
}
}
19 校验和
在文本文件中1位的修改通常只会影响一个字符,但是在压缩文件中,1位的错误将会导致整个文件无法读取。
所以在压缩文件末尾加上校验和已确保文件是完好无损的。
当使用1位校验和时,往往不能满足要求,
如果使用8位,然后对65536求余,这样几乎结果会随机出现在任意位置,从而有更好的效果
当然,精心设计的校验和会有更好的效果。
接口:
java.util.zip.Checksum
需要实现的方法:
public abstract void update(int b)
public abstract void update(byte[] data, int offset, int length)
public abstract long getValue( )
public abstract void reset( )
例如:
CRC32 and Adler32,他们都在java.util.zip包中。
除了直接使用这两个类的update方式来进行更新以外,考虑流:
public CheckedInputStream(InputStream in, Checksum cksum) public CheckedOutputStream(OutputStream out, Checksum cksum)
FileInputStream fin = new FileInputStream("/etc/passwd");
Checksum cksum = new CRC32( );
CheckedInputStream cin = new CheckedInputStream(fin, cksum);