Java 1.1也添加一个类,用以支持对压缩格式的数据流的读写。它们封装到现成的IO类中,以提供压缩功能。

此时Java 1.1的一个问题显得非常突出:它们不是从新的ReaderWriter类派生出来的,而是属于InputStreamOutputStream层次结构的一部分。所以有时不得不混合使用两种类型的数据流(注意可用InputStreamReaderOutputStreamWriter在不同的类型间方便地进行转换)。

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接口非常简单,所以如果只有单个数据流需要压缩(而不是一系列不同的数据),那么它就可能是最适当选择。下面是对单个文件进行压缩的例子:

  1. //: GZIPcompress.java
  2. // Uses Java 1.1 GZIP compression to compress
  3. // a file whose name is passed on the command
  4. // line.
  5. import java.io.*;
  6. import java.util.zip.*;
  7. public class GZIPcompress {
  8. public static void main(String[] args) {
  9. try {
  10. BufferedReader in =
  11. new BufferedReader(
  12. new FileReader(args[0]));
  13. BufferedOutputStream out =
  14. new BufferedOutputStream(
  15. new GZIPOutputStream(
  16. new FileOutputStream("test.gz")));
  17. System.out.println("Writing file");
  18. int c;
  19. while((c = in.read()) != -1)
  20. out.write(c);
  21. in.close();
  22. out.close();
  23. System.out.println("Reading file");
  24. BufferedReader in2 =
  25. new BufferedReader(
  26. new InputStreamReader(
  27. new GZIPInputStream(
  28. new FileInputStream("test.gz"))));
  29. String s;
  30. while((s = in2.readLine()) != null)
  31. System.out.println(s);
  32. } catch(Exception e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. } ///:~

压缩类的用法非常直观——只需将输出流封装到一个GZIPOutputStream或者ZipOutputStream内,并将输入流封装到GZIPInputStream或者ZipInputStream内即可。剩余的全部操作就是标准的IO读写。然而,这是一个很典型的例子,我们不得不混合使用新旧IO流:数据的输入使用Reader类,而GZIPOutputStream的构造器只能接收一个OutputStream对象,不能接收Writer对象。

10.8.2 用Zip进行多文件保存

提供了Zip支持的Java 1.1库显得更加全面。利用它可以方便地保存多个文件。甚至有一个独立的类来简化对Zip文件的读操作。这个库采采用的是标准Zip格式,所以能与当前因特网上使用的大量压缩、解压工具很好地协作。下面这个例子采取了与前例相同的形式,但能根据我们需要控制任意数量的命令行参数。除此之外,它展示了如何用Checksum类来计算和校验文件的“校验和”(Checksum)。可选用两种类型的ChecksumAdler32(速度要快一些)和CRC32(慢一些,但更准确)。

  1. //: ZipCompress.java
  2. // Uses Java 1.1 Zip compression to compress
  3. // any number of files whose names are passed
  4. // on the command line.
  5. import java.io.*;
  6. import java.util.*;
  7. import java.util.zip.*;
  8. public class ZipCompress {
  9. public static void main(String[] args) {
  10. try {
  11. FileOutputStream f =
  12. new FileOutputStream("test.zip");
  13. CheckedOutputStream csum =
  14. new CheckedOutputStream(
  15. f, new Adler32());
  16. ZipOutputStream out =
  17. new ZipOutputStream(
  18. new BufferedOutputStream(csum));
  19. out.setComment("A test of Java Zipping");
  20. // Can't read the above comment, though
  21. for(int i = 0; i < args.length; i++) {
  22. System.out.println(
  23. "Writing file " + args[i]);
  24. BufferedReader in =
  25. new BufferedReader(
  26. new FileReader(args[i]));
  27. out.putNextEntry(new ZipEntry(args[i]));
  28. int c;
  29. while((c = in.read()) != -1)
  30. out.write(c);
  31. in.close();
  32. }
  33. out.close();
  34. // Checksum valid only after the file
  35. // has been closed!
  36. System.out.println("Checksum: " +
  37. csum.getChecksum().getValue());
  38. // Now extract the files:
  39. System.out.println("Reading file");
  40. FileInputStream fi =
  41. new FileInputStream("test.zip");
  42. CheckedInputStream csumi =
  43. new CheckedInputStream(
  44. fi, new Adler32());
  45. ZipInputStream in2 =
  46. new ZipInputStream(
  47. new BufferedInputStream(csumi));
  48. ZipEntry ze;
  49. System.out.println("Checksum: " +
  50. csumi.getChecksum().getValue());
  51. while((ze = in2.getNextEntry()) != null) {
  52. System.out.println("Reading file " + ze);
  53. int x;
  54. while((x = in2.read()) != -1)
  55. System.out.write(x);
  56. }
  57. in2.close();
  58. // Alternative way to open and read
  59. // zip files:
  60. ZipFile zf = new ZipFile("test.zip");
  61. Enumeration e = zf.entries();
  62. while(e.hasMoreElements()) {
  63. ZipEntry ze2 = (ZipEntry)e.nextElement();
  64. System.out.println("File: " + ze2);
  65. // ... and extract the data as before
  66. }
  67. } catch(Exception e) {
  68. e.printStackTrace();
  69. }
  70. }
  71. } ///:~

对于要加入压缩档的每一个文件,都必须调用putNextEntry(),并将其传递给一个ZipEntry对象。ZipEntry对象包含了一个功能全面的接口,利用它可以获取和设置Zip文件内那个特定的Entry(入口)上能够接受的所有数据:名字、压缩后和压缩前的长度、日期、CRC校验和、额外字段的数据、注释、压缩方法以及它是否一个目录入口等等。然而,虽然Zip格式提供了设置密码的方法,但Java的Zip库没有提供这方面的支持。而且尽管CheckedInputStreamCheckedOutputStream同时提供了对Adler32CRC32校验和的支持,但是ZipEntry只支持CRC的接口。这虽然属于基层Zip格式的限制,但却限制了我们使用速度更快的Adler32

为解压文件,ZipInputStream提供了一个getNextEntry()方法,能在有的前提下返回下一个ZipEntry。作为一个更简洁的方法,可以用ZipFile对象读取文件。该对象有一个entries()方法,可以为ZipEntry返回一个Enumeration(枚举)。

为读取校验和,必须多少拥有对关联的Checksum对象的访问权限。在这里保留了指向CheckedOutputStreamCheckedInputStream对象的一个引用。但是,也可以只占有指向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配套提供,可以按我们的选择自动压缩文件。请在命令行调用它:

  1. jar [选项] 说明 [详情单] 输入文件

其中,“选项”用一系列字母表示(不必输入连字号或其他任何指示符)。如下所示:

  1. c 创建新的或空的压缩档
  2. t 列出目录表
  3. x 解压所有文件
  4. x file 解压指定文件
  5. f 指出“我准备向你提供文件名”。若省略此参数,jar会假定它的输入来自标准输入;或者在它创建文件时,输出会进入标准输出内
  6. m 指出第一个参数将是用户自建的详情表文件的名字
  7. v 产生详细输出,对jar做的工作进行巨细无遗的描述
  8. O 只保存文件;不压缩文件(用于创建一个JAR文件,以便我们将其置入自己的类路径中)
  9. M 不自动生成详情表文件

在准备进入JAR文件的文件中,若包括了一个子目录,那个子目录会自动添加,其中包括它自己的所有子目录,以此类推。路径信息也会得到保留。

下面是调用jar的一些典型方法:

  1. jar cf myJarFile.jar *.class

用于创建一个名为myJarFile.jar的JAR文件,其中包含了当前目录中的所有类文件,同时还有自动产生的详情表文件。

  1. jar cmf myJarFile.jar myManifestFile.mf *.class

与前例类似,但添加了一个名为myManifestFile.mf的用户自建详情表文件。

  1. jar tf myJarFile.jar

生成myJarFile.jar内所有文件的一个目录表。

  1. jar tvf myJarFile.jar

添加verbose(详尽)标志,提供与myJarFile.jar中的文件有关的、更详细的资料。

  1. jar cvf myApp.jar audio classes image

假定audioclassesimage是子目录,这样便将所有子目录合并到文件myApp.jar中。其中也包括了verbose标志,可在jar程序工作时反馈更详尽的信息。

如果用O选项创建了一个JAR文件,那个文件就可置入自己的类路径(CLASSPATH)中:

  1. CLASSPATH="lib1.jar;lib2.jar;"

Java能在lib1.jarlib2.jar中搜索目标类文件。

jar工具的功能没有zip工具那么丰富。例如,不能够添加或更新一个现成JAR文件中的文件,只能从头开始新建一个JAR文件。此外,不能将文件移入一个JAR文件,并在移动后将它们删除。然而,在一种平台上创建的JAR文件可在其他任何平台上由jar工具毫无阻碍地读出(这个问题有时会困扰zip工具)。

正如大家在第13章会看到的那样,我们也用JAR为Java Beans打包。