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 打包。

上一页
下一页