10.8 压缩
10.8 压缩
Java 1.1 也添加一个类,用以支持对压缩格式的数据流的读写。它们封装到现成的 IO 类中,以提供压缩功能。
此时 Java 1.1 的一个问题显得非常突出:它们不是从新的 Reader 和 Writer 类衍生出来的,而是属于 InputStream 和 OutputStream 层次结构的一部分。所以有时不得不混合使用两种类型的数据流(注意可用 InputStreamReader 和 OutputStreamWriter 在不同的类型间方便地进行转换)。
Java 1.1 压缩类 功能
CheckedInputStream GetCheckSum()为任何InputStream产生校验和(不仅是解压)
CheckedOutputStream GetCheckSum()为任何OutputStream产生校验和(不仅是解压)
DeflaterOutputStream 用于压缩类的基础类
ZipOutputStream 一个DeflaterOutputStream,将数据压缩成Zip文件格式
GZIPOutputStream 一个DeflaterOutputStream,将数据压缩成GZIP文件格式
InflaterInputStream 用于解压类的基础类
ZipInputStream 一个DeflaterInputStream,解压用Zip文件格式保存的数据
GZIPInputStream 一个DeflaterInputStream,解压用GZIP文件格式保存的数据
尽管存在许多种压缩算法,但是 Zip 和 GZIP 可能最常用的。所以能够很方便地用多种现成的工具来读写这些格式的压缩数据。
10.8.1 用 GZIP 进行简单压缩
GZIP 接口非常简单,所以如果只有单个数据流需要压缩(而不是一系列不同的数据),那么它就可能是最适当选择。下面是对单个文件进行压缩的例子:
//: GZIPcompress.java
// Uses Java 1.1 GZIP compression to compress
// a file whose name is passed on the command
// line.
import java.io.*;
import java.util.zip.*;
public class GZIPcompress {
public static void main(String[] args) {
try {
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);
} catch(Exception e) {
e.printStackTrace();
}
}
} ///:~
压缩类的用法非常直观——只需将输出流封装到一个 GZIPOutputStream 或者 ZipOutputStream 内,并将输入流封装到 GZIPInputStream 或者 ZipInputStream 内即可。剩余的全部操作就是标准的 IO 读写。然而,这是一个很典型的例子,我们不得不混合使用新旧 IO 流:数据的输入使用 Reader 类,而 GZIPOutputStream 的构造器只能接收一个 OutputStream 对象,不能接收 Writer 对象。
10.8.2 用 Zip 进行多文件保存
提供了 Zip 支持的 Java 1.1 库显得更加全面。利用它可以方便地保存多个文件。甚至有一个独立的类来简化对 Zip 文件的读操作。这个库采采用的是标准 Zip 格式,所以能与当前因特网上使用的大量压缩、解压工具很好地协作。下面这个例子采取了与前例相同的形式,但能根据我们需要控制任意数量的命令行参数。除此之外,它展示了如何用 Checksum 类来计算和校验文件的“校验和”(Checksum)。可选用两种类型的 Checksum:Adler32(速度要快一些)和 CRC32(慢一些,但更准确)。
//: ZipCompress.java
// Uses Java 1.1 Zip compression to compress
// any number of files whose names are passed
// on the command line.
import java.io.*;
import java.util.*;
import java.util.zip.*;
public class ZipCompress {
public static void main(String[] args) {
try {
FileOutputStream f =
new FileOutputStream("test.zip");
CheckedOutputStream csum =
new CheckedOutputStream(
f, new Adler32());
ZipOutputStream out =
new ZipOutputStream(
new BufferedOutputStream(csum));
out.setComment("A test of Java Zipping");
// Can't read the above comment, though
for(int i = 0; i < args.length; i++) {
System.out.println(
"Writing file " + args[i]);
BufferedReader in =
new BufferedReader(
new FileReader(args[i]));
out.putNextEntry(new ZipEntry(args[i]));
int c;
while((c = in.read()) != -1)
out.write(c);
in.close();
}
out.close();
// Checksum valid only after the file
// has been closed!
System.out.println("Checksum: " +
csum.getChecksum().getValue());
// Now extract the files:
System.out.println("Reading file");
FileInputStream fi =
new FileInputStream("test.zip");
CheckedInputStream csumi =
new CheckedInputStream(
fi, new Adler32());
ZipInputStream in2 =
new ZipInputStream(
new BufferedInputStream(csumi));
ZipEntry ze;
System.out.println("Checksum: " +
csumi.getChecksum().getValue());
while((ze = in2.getNextEntry()) != null) {
System.out.println("Reading file " + ze);
int x;
while((x = in2.read()) != -1)
System.out.write(x);
}
in2.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();
System.out.println("File: " + ze2);
// ... and extract the data as before
}
} catch(Exception e) {
e.printStackTrace();
}
}
} ///:~
对于要加入压缩档的每一个文件,都必须调用 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 库时并不仅仅限于文件——可以压缩任何东西,包括要通过网络连接发送的数据。
10.8.3 Java 归档(jar)实用程序
Zip 格式亦在 Java 1.1 的 JAR(Java ARchive)文件格式中得到了采用。这种文件格式的作用是将一系列文件合并到单个压缩文件里,就象 Zip 那样。然而,同 Java 中其他任何东西一样,JAR 文件是跨平台的,所以不必关心涉及具体平台的问题。除了可以包括声音和图像文件以外,也可以在其中包括类文件。
涉及因特网应用时,JAR 文件显得特别有用。在 JAR 文件之前,Web 浏览器必须重复多次请求 Web 服务器,以便下载完构成一个“程序片”(Applet)的所有文件。除此以外,每个文件都是未经压缩的。但在将所有这些文件合并到一个 JAR 文件里以后,只需向远程服务器发出一次请求即可。同时,由于采用了压缩技术,所以可在更短的时间里获得全部数据。另外,JAR 文件里的每个入口(条目)都可以加上数字化签名(详情参考 Java 用户文档)。
一个 JAR 文件由一系列采用 Zip 压缩格式的文件构成,同时还有一张“详情单”,对所有这些文件进行了描述(可创建自己的详情单文件;否则,jar 程序会为我们代劳)。在联机用户文档中,可以找到与 JAR 详情单更多的资料(详情单的英语是“Manifest”)。 jar 实用程序已与 Sun 的 JDK 配套提供,可以按我们的选择自动压缩文件。请在命令行调用它:
jar [选项] 说明 [详情单] 输入文件
其中,“选项”用一系列字母表示(不必输入连字号或其他任何指示符)。如下所示:
c 创建新的或空的压缩档
t 列出目录表
x 解压所有文件
x file 解压指定文件
f 指出“我准备向你提供文件名”。若省略此参数,jar会假定它的输入来自标准输入;或者在它创建文件时,输出会进入标准输出内
m 指出第一个参数将是用户自建的详情表文件的名字
v 产生详细输出,对jar做的工作进行巨细无遗的描述
O 只保存文件;不压缩文件(用于创建一个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 程序工作时反馈更详尽的信息。
如果用 O 选项创建了一个 JAR 文件,那个文件就可置入自己的类路径(CLASSPATH)中:
CLASSPATH="lib1.jar;lib2.jar;"
Java 能在 lib1.jar 和 lib2.jar 中搜索目标类文件。
jar 工具的功能没有 zip 工具那么丰富。例如,不能够添加或更新一个现成 JAR 文件中的文件,只能从头开始新建一个 JAR 文件。此外,不能将文件移入一个 JAR 文件,并在移动后将它们删除。然而,在一种平台上创建的 JAR 文件可在其他任何平台上由 jar 工具毫无阻碍地读出(这个问题有时会困扰 zip 工具)。
正如大家在第 13 章会看到的那样,我们也用 JAR 为 Java Beans 打包。