Java IO 类库中的类支持读写格式的数据流,你可以用它们对其他的IO类进行封装,以提供压缩功能。
这些类不是从新的Reader 和Writer 类衍生出来的,而是属于InputStream 和OutputStream 层次结构的一部分。这样做是因为压缩类库是按字节方式而不是字符方式处理的。所以有时不得不混合使用两种类型的数据流(注意可用InputStreamReader 和OutputStreamWriter 在不同的类型间方便地进行转换)。
压缩类 | 功能 |
---|---|
CheckedInputStream | GetCheckSum()为任何InputStream 产生校验和(不仅是解压) |
CheckedOutputStream | GetCheckSum()为任何OutputStream 产生校验和(不仅是解压) |
DeflaterOutputStream | 用于压缩类的基础类 |
ZipOutputStream | 一个DeflaterOutputStream,将数据压缩成Zip 文件格式 |
GZIPOutputStream | 一个DeflaterOutputStream,将数据压缩成GZIP 文件格式 |
InflaterInputStream | 用于解压类的基础类 |
ZipInputStream | 一个DeflaterInputStream,解压用Zip 文件格式保存的数据 |
GZIPInputStream | 一个DeflaterInputStream,解压用GZIP 文件格式保存的数据 |
尽管存在许多种压缩算法,但是Zip 和GZIP 可能最常用的。所以能够很方便地用多种现成的工具来读写这些格式的压缩数据。
1.用G Z I P 进行简单压缩
GZIP 接口非常简单,所以如果只有单个数据流需要压缩(而不是一系列不同的数据),那么它就可能是最适当选择。下面是对单个文件进行压缩的例子:
//: io/GZIPcompress.java
// {Args: GZIPcompress.java}
import java.util.zip.*;
import java.io.*;
public class GZIPcompress {
public static void main(String[] args)
throws IOException {
if(args.length == 0) {
System.out.println(
"Usage: \nGZIPcompress file\n" +
"\tUses GZIP compression to compress " +
"the file to test.gz");
System.exit(1);
}
BufferedReader in = new BufferedReader(
new FileReader(args[0]));
BufferedOutputStream out = new BufferedOutputStream(
new GZIPOutputStream(
new FileOutputStream("test.gz")));
System.out.println("Writing file");
int c;
while((c = in.read()) != -1)
out.write(c);
in.close();
out.close();
System.out.println("Reading file");
BufferedReader in2 = new BufferedReader(
new InputStreamReader(new GZIPInputStream(
new FileInputStream("test.gz"))));
String s;
while((s = in2.readLine()) != null)
System.out.println(s);
}
} /* (Execute to see output) *///:~
压缩类的用法非常直观——只需将输出流封装到一个GZIPOutputStream 或者ZipOutputStream 内,并将输入流封装到GZIPInputStream 或者ZipInputStream 内即可。剩余的全部操作就是标准的IO 读写。然而,这是一个很典型的例子,我们不得不混合使用新旧IO 流:数据的输入使用Reader 类,而GZIPOutputStream 的构建器只能接收一个OutputStream 对象,不能接收Writer 对象。
2.用Z i p 进行多文件保存
支持Zip格式的Java库更加全面。利用该库可以方便地保存多个文件。甚至有一个独立的类来简化对Zip 文件的读操作。这个库采用的是标准Zip 格式,所以能与当前因特网上使用的大量压缩、解压工具很好地协作。下面这个例子采取了与前例相同的形式,但能根据我们需要控制任意数量的命令行参数。除此之外,它展示了如何用Checksum 类来计算和校验文件的“校验和”(Checksum )。可选用两种类型的Checksum:Adler32(速度要快一些)和CRC32(慢一些,但更准确)。
//: io/ZipCompress.java
// Uses Zip compression to compress any
// number of files given on the command line.
// {Args: ZipCompress.java}
import java.util.zip.*;
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class ZipCompress {
public static void main(String[] args)
throws IOException {
FileOutputStream f = new FileOutputStream("test.zip");
CheckedOutputStream csum =
new CheckedOutputStream(f, new Adler32());
ZipOutputStream zos = new ZipOutputStream(csum);
BufferedOutputStream out =
new BufferedOutputStream(zos);
zos.setComment("A test of Java Zipping");
// No corresponding getComment(), though.
for(String arg : args) {
print("Writing file " + arg);
BufferedReader in =
new BufferedReader(new FileReader(arg));
zos.putNextEntry(new ZipEntry(arg));
int c;
while((c = in.read()) != -1)
out.write(c);
in.close();
out.flush();
}
out.close();
// Checksum valid only after the file has been closed!
print("Checksum: " + csum.getChecksum().getValue());
// Now extract the files:
print("Reading file");
FileInputStream fi = new FileInputStream("test.zip");
CheckedInputStream csumi =
new CheckedInputStream(fi, new Adler32());
ZipInputStream in2 = new ZipInputStream(csumi);
BufferedInputStream bis = new BufferedInputStream(in2);
ZipEntry ze;
while((ze = in2.getNextEntry()) != null) {
print("Reading file " + ze);
int x;
while((x = bis.read()) != -1)
System.out.write(x);
}
if(args.length == 1)
print("Checksum: " + csumi.getChecksum().getValue());
bis.close();
// Alternative way to open and read Zip files:
ZipFile zf = new ZipFile("test.zip");
Enumeration e = zf.entries();
while(e.hasMoreElements()) {
ZipEntry ze2 = (ZipEntry)e.nextElement();
print("File: " + ze2);
// ... and extract the data as before
}
/* if(args.length == 1) */
}
} /* (Execute to see output) *///:~
对于要加入压缩档的每一个文件,都必须调用putNextEntry(),并将其传递给一个ZipEntry 对象。ZipEntry 对象包含了一个功能全面的接口,利用它可以获取和设置Zip 文件内那个特定的Entry(入口)上能够接受的所有数据:名字、压缩后和压缩前的长度、日期、CRC 校验和、额外字段的数据、注释、压缩方法以及它是否一个目录入口等等。然而,虽然Zip 格式提供了设置密码的方法,但Java 的Zip 库没有提供这方面的支持。而且尽管CheckedInputStream 和CheckedOutputStream 同时提供了对Adler32 和CRC32 校验和的支持,但是ZipEntry 只支持CRC 的接口。这虽然属于基层Zip 格式的限制,但却限制了我们使用速度更快的Adler32。
为解压文件,ZipInputStream 提供了一个getNextEntry()方法,能在有的前提下返回下一个ZipEntry。作为一个更简洁的方法,可以用ZipFile 对象读取文件。该对象有一个entries()方法,可以为ZipEntry 返回一个Enumeration(枚举)。
为读取校验和,必须拥有对关联的Checksum 对象的访问权限。在这里保留了指向CheckedOutputStream和CheckedInputStream 对象的引用。但是,也可以只占有指向Checksum 对象的引用。Zip 流中一个令人困惑的方法setComment()。正如前面展示的那样,我们可在写一个文件时设置注释内容,但却没有办法取出ZipInputStream 内的注释。看起来,似乎只能通过ZipEntry 逐个入口地提供对注释的完全支持。
当然,使用GZIP 或Zip 库时并不仅仅限于文件——可以压缩任何东西,包括要通过网络连接发送的数据。
3.Java档案文件
Zip 格式在JAR(Java ARchive)文件格式中得到了采用。这种文件格式的作用是将一系列文件合并到单个压缩文件里,就象Zip 那样。然而,同Java 中其他任何东西一样,JAR 文件是跨平台的,所以不必关心涉及具体平台的问题。除了可以包括声音和图像文件以外,也可以在其中包括类文件。
JAR文件非常有用,尤其在涉及因特网应用时。在JAR 文件之前,Web 浏览器必须重复多次请求Web 服务器,以便下载完构成一个“程片”(Applet)的所有文件。除此以外,每个文件都是未经压缩的。但在将所有这些文件合并到一个JAR 文件里以后,只需向远程服务器发出一次请求即可。同时,由于采用了压缩技术,所以可在更短的时间里获得全部数据。另外,JAR 文件里的每个入口(条目)都可以加上数字化签名(详情参考Java 用户文档)。
一个JAR 文件由一系列采用Zip 压缩格式的文件构成,同时还有一张“详情单”,对所有这些文件进行了描述(可创建自己的详情单文件;否则,jar 程序会为我们代劳)。在联机用户文档中,可以找到与JAR 详情单更多的资料(详情单的英语是“Manifest”)。
jar 实用程序已与Sun 的JDK 配套提供,可以按我们的选择自动压缩文件。请在命令行调用它:
jar [选项] 说明 [详情单] 输入文件
option | 描述 |
---|---|
c | 创建新的或空的压缩档 |
t | 列出目录表 |
x | 解压所有文件 |
x file | 解压指定文件 |
f | 指出“我准备向你提供文件名”。若省略此参数,jar 会假定它的输入来自标准输入;或者在它创建文件时,输出会进入标准输出内 |
m | 指出第一个参数将是用户自建的详情表文件的名字 |
v | 产生详细输出,描述jar所做的工作 |
0(数字零) | 只保存文件;不压缩文件(用于创建一个JAR 文件,以便我们将其置入自己的类路径中) |
M | 不自动生成详情表文件 |
在准备进入JAR 文件的文件中,若包括了一个子目录,那个子目录会自动添加,其中包括它自己的所有子目录,以此类推。路径信息也会得到保留。
下面是调用jar 的一些典型方法:
jar cf myJarFile.jar *.class
用于创建一个名为myJarFile.jar 的JAR 文件,其中包含了当前目录中的所有类文件,同时还有自动产生的详情表文件。
jar cmf myJarFile.jar myManifestFile.mf *.class
与前例类似,但添加了一个名为myManifestFile.mf 的用户自建详情表文件。
jar tf myJarFile.jar
生成myJarFile.jar 内所有文件的一个目录表。
jar tvf myJarFile.jar
添加“verbose”(详尽)标志,提供与myJarFile.jar 中的文件有关的、更详细的资料。
jar cvf myApp.jar audio classes image
假定audio,classes 和image 是子目录,这样便将所有子目录合并到文件myApp.jar 中。其中也包括了“verbose”标志,可在jar 程序工作时反馈更详尽的信息。
如果用0(零)选项创建了一个JAR 文件,那个文件就可置入自己的类路径(CLASSPATH)中:
CLASSPATH=”lib1.jar;lib2.jar;”
Java 能在lib1.jar 和lib2.jar 中搜索目标类文件。