# 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打包。
- Java 編程思想
- 寫在前面的話
- 引言
- 第1章 對象入門
- 1.1 抽象的進步
- 1.2 對象的接口
- 1.3 實現方案的隱藏
- 1.4 方案的重復使用
- 1.5 繼承:重新使用接口
- 1.6 多態對象的互換使用
- 1.7 對象的創建和存在時間
- 1.8 異常控制:解決錯誤
- 1.9 多線程
- 1.10 永久性
- 1.11 Java和因特網
- 1.12 分析和設計
- 1.13 Java還是C++
- 第2章 一切都是對象
- 2.1 用引用操縱對象
- 2.2 所有對象都必須創建
- 2.3 絕對不要清除對象
- 2.4 新建數據類型:類
- 2.5 方法、參數和返回值
- 2.6 構建Java程序
- 2.7 我們的第一個Java程序
- 2.8 注釋和嵌入文檔
- 2.9 編碼樣式
- 2.10 總結
- 2.11 練習
- 第3章 控制程序流程
- 3.1 使用Java運算符
- 3.2 執行控制
- 3.3 總結
- 3.4 練習
- 第4章 初始化和清除
- 4.1 用構造器自動初始化
- 4.2 方法重載
- 4.3 清除:收尾和垃圾收集
- 4.4 成員初始化
- 4.5 數組初始化
- 4.6 總結
- 4.7 練習
- 第5章 隱藏實現過程
- 5.1 包:庫單元
- 5.2 Java訪問指示符
- 5.3 接口與實現
- 5.4 類訪問
- 5.5 總結
- 5.6 練習
- 第6章 類復用
- 6.1 組合的語法
- 6.2 繼承的語法
- 6.3 組合與繼承的結合
- 6.4 到底選擇組合還是繼承
- 6.5 protected
- 6.6 累積開發
- 6.7 向上轉換
- 6.8 final關鍵字
- 6.9 初始化和類裝載
- 6.10 總結
- 6.11 練習
- 第7章 多態性
- 7.1 向上轉換
- 7.2 深入理解
- 7.3 覆蓋與重載
- 7.4 抽象類和方法
- 7.5 接口
- 7.6 內部類
- 7.7 構造器和多態性
- 7.8 通過繼承進行設計
- 7.9 總結
- 7.10 練習
- 第8章 對象的容納
- 8.1 數組
- 8.2 集合
- 8.3 枚舉器(迭代器)
- 8.4 集合的類型
- 8.5 排序
- 8.6 通用集合庫
- 8.7 新集合
- 8.8 總結
- 8.9 練習
- 第9章 異常差錯控制
- 9.1 基本異常
- 9.2 異常的捕獲
- 9.3 標準Java異常
- 9.4 創建自己的異常
- 9.5 異常的限制
- 9.6 用finally清除
- 9.7 構造器
- 9.8 異常匹配
- 9.9 總結
- 9.10 練習
- 第10章 Java IO系統
- 10.1 輸入和輸出
- 10.2 增添屬性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File類
- 10.5 IO流的典型應用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 壓縮
- 10.9 對象序列化
- 10.10 總結
- 10.11 練習
- 第11章 運行期類型識別
- 11.1 對RTTI的需要
- 11.2 RTTI語法
- 11.3 反射:運行期類信息
- 11.4 總結
- 11.5 練習
- 第12章 傳遞和返回對象
- 12.1 傳遞引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只讀類
- 12.5 總結
- 12.6 練習
- 第13章 創建窗口和程序片
- 13.1 為何要用AWT?
- 13.2 基本程序片
- 13.3 制作按鈕
- 13.4 捕獲事件
- 13.5 文本字段
- 13.6 文本區域
- 13.7 標簽
- 13.8 復選框
- 13.9 單選鈕
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 視窗化應用
- 13.16 新型AWT
- 13.17 Java 1.1用戶接口API
- 13.18 可視編程和Beans
- 13.19 Swing入門
- 13.20 總結
- 13.21 練習
- 第14章 多線程
- 14.1 反應靈敏的用戶界面
- 14.2 共享有限的資源
- 14.3 堵塞
- 14.4 優先級
- 14.5 回顧runnable
- 14.6 總結
- 14.7 練習
- 第15章 網絡編程
- 15.1 機器的標識
- 15.2 套接字
- 15.3 服務多個客戶
- 15.4 數據報
- 15.5 一個Web應用
- 15.6 Java與CGI的溝通
- 15.7 用JDBC連接數據庫
- 15.8 遠程方法
- 15.9 總結
- 15.10 練習
- 第16章 設計模式
- 16.1 模式的概念
- 16.2 觀察器模式
- 16.3 模擬垃圾回收站
- 16.4 改進設計
- 16.5 抽象的應用
- 16.6 多重分發
- 16.7 訪問器模式
- 16.8 RTTI真的有害嗎
- 16.9 總結
- 16.10 練習
- 第17章 項目
- 17.1 文字處理
- 17.2 方法查找工具
- 17.3 復雜性理論
- 17.4 總結
- 17.5 練習
- 附錄A 使用非JAVA代碼
- 附錄B 對比C++和Java
- 附錄C Java編程規則
- 附錄D 性能
- 附錄E 關于垃圾收集的一些話
- 附錄F 推薦讀物