ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 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打包。