java.util.zip 包,包含了6個流類和其他6個其他各式各樣用於讀寫zip,gzip和其他解壓縮格式。
Java使用這些類來讀取寫入JAR文檔和顯示PNG圖片。
你可以將這些工具用於通用的壓縮和解壓縮過程。
由於有了這些類,壓縮和解壓縮變得很容易。
2 Inflaters and Deflaters (解壓者和壓縮者)
java.util.zip.Deflater 和 java.util.zip.Inflater 爲其他類提供壓縮和解壓的能力。
它們是Java的壓縮和解壓的引擎
大部分都是CPU密集型計算,所以底層使用了C來寫.
3 壓縮數據
Deflater類包含了壓縮塊數據的方法。
你可以選擇壓縮格式,級別和策略。
Deflater9步完成壓縮:
1. 構造Deflater對象。
2. 選擇策略(可選)。
3. 設置壓縮級別(可選)。
4. 重置字典(可選)。
5. 設置輸入
6. 重複壓縮數據,直到needsInput()返回true
7. 如果有更多的可用數據,返回第5步繼續添加。
否則,進行第8步
8. 完成數據
9. 如果有更多的流需要壓縮,重啓壓縮器。
4 構造解壓對象
三個構造函數:
public Deflater(int level, boolean useGzip)
public Deflater(int level)
public Deflater( )
level越大,通常壓縮時間越長,壓縮得到的包越小
例如:
public static final int NO_COMPRESSION = 0;
public static final int BEST_SPEED = 1;
public static final int BEST_COMPRESSION = 9;
public static final int DEFAULT_COMPRESSION = -1;
如果useGzip爲true,則使用gzip;否則,使用zlib格式。
5 策略選擇
Java支持的壓縮策略包括:filtered, Huffman, and default
public void setStrategy(int strategy)
支持:
Deflater.FILTERED = 1;
Deflater.HUFFMAN_ONLY = 2;
Deflater.DEFAULT_STRATEGY = -1;
如果出現不支持的策略,拋出IllegalArgumentException
默認策略:總是重複的詞
哈弗曼編碼:各個詞頻不一樣
過濾器:可以妥協的2進制數據(例如音頻視頻之類)
6 設置壓縮級別
除了在構造函數中進行級別的指定,你還可以通過如下方法:
public void setLevel(int Level)
來實現對壓縮級別的指定。
總的而言,壓縮速度和壓縮質量總是負相關的
好的編碼方式是使用下面幾個常量中的一個:
Deflater.NO_COMPRESSION (0),
Deflater.BEST_SPEED (1),
Deflater.BEST_COMPRESSION (9),
Deflater.DEFAULT_COMPRESSION (-1)
而不是一個顯式的值。
在小文件上做的測試,發現0~9直接差別不大。
對於GIF, JPEG, or PNG這類圖片文件,他們具有內置的壓縮方式,所以通用的壓縮反而可能增加他們的體積。
7 設置壓縮字典
默認的壓縮字典是根據順序來進行的,它將最先讀到的內容放置到字典中。第二次讀到這個詞時,將它用對應字典的位置來替換。
你可以爲你的壓縮文件預設這樣的文本值
額外值得說明的是:由於文件直接差別很大,而設置字典通常需要字典本身的存儲空間,所以設計上一定要小心謹慎。
8 其他過程
byte[] originalBytes = ...
Deflater deflater = new Deflater();
deflater.setInput(originalBytes);
deflater.finish();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
while (!deflater.finished()) {
int byteCount = deflater.deflate(buf);
baos.write(buf, 0, byteCount);
}
deflater.end();
byte[] compressedBytes = baos.toByteArray();
設置輸入,壓縮完成,將壓縮得到內容寫到內存中。
如果你需要使用相同的策略,無需創建一個新的壓縮器,而是使用壓縮器的reset()方法
9 檢查解壓器的狀態
Deflater提供了幾個用於檢查狀態的方法
public int getAdler( )
提供了對未壓縮內容的32位地址校驗和。
public int getTotalIn( )
返回setInput中設置的字節數
public int getTotalOut( )
返回deflate方法返回的字節數
例如下面這個代碼,說明了壓縮了多少:
System.out.println((1.0 - def.getTotalOut()/def.getTotalIn( ))*100.0 +
"% saved");
10 解壓器
解壓器包含了用於解壓zip, gzip, or zlib格式的方法。由於參數較少,使用比Deflater 要簡單。
你需要做下面幾部:
1 構件一個 Inflater對象,
2 設置輸入經過壓縮了的數據
3 調用needsDictionary( )判斷是否需要預置字典
4 如果需要字典,則使用getAdler( ) 獲得字典的32位地址校驗和,然後調用setDictionary( )來設置字典
5 解壓數據直到inflate( ) 返回0
6 如果needsInput( )返回真,返回第2步繼續添加數據
7 使用finished()來返回true
如果需要使用Inflater 解壓更多數據,重置它。
你通常很少使用這個類,考慮使用InflaterInputStream 或 InflaterOutputStream。
11 解壓其他操作
可類比參考壓縮流:
byte[] compressedBytes = ...
int decompressedByteCount = ... // From your format's metadata.
Inflater inflater = new Inflater();
inflater.setInput(compressedBytes, 0, compressedBytes.length);
byte[] decompressedBytes = new byte[decompressedByteCount];
if (inflater.inflate(decompressedBytes) != decompressedByteCount) {
throw new AssertionError();
}
inflater.end();
12 檢查解壓器的狀態
public int getAdler( )獲得32位校驗和
public int getTotalIn( )返回setInput( )設置的字節數
public int getTotalOut( )返回通過inflate( )總數
public int getRemaining( )返回還剩下的壓縮字節數
13 高級壓縮流
總而言之,Deflater和Inflater有一些原始。所以我們推薦使用:
java.util.zip.DeflaterOutputStream 類是一個過濾流,它在將內容寫到潛在的流之前會進行壓縮。
java.util.zip.InflaterInputStream 類將在把內容傳遞到程序之前進行一個解壓。
java.util.zip.GZIPInputStream 和java.util.zip.GZIPOutputStream 與上面做的事情相同,不過他是GZIP
14 壓縮流使用例子
你就像使用別的過濾流一樣使用就好
import java.io.*;
import java.util.zip.*;
public class GZipper {
public final static String GZIP_SUFFIX = ".gz";
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
try {
InputStream fin = new FileInputStream(args[i]);
OutputStream fout = new FileOutputStream(args[i] + GZIP_SUFFIX);
GZIPOutputStream gzout = new GZIPOutputStream(fout);
for (int c = fin.read(); c != -1; c = fin.read( )) {
gzout.write(c);
}
gzout.close( );
}
catch (IOException ex) {
System.err.println(ex);
}
}
}
}
15 Zip文件
Gzip僅僅是壓縮方式,而zip則既是壓縮方式,又是歸檔方式。
java.util.zip.ZipFile代表了整個文檔。
java.util.zip.ZipEntry代表了在歸檔文件中的單獨的一個文件。
ZipFile擁有多個構造方法
public ZipFile(String filename) throws ZipException, IOException
public ZipFile(File file) throws ZipException, IOException
public ZipFile(File file, int mode) throws IOException
其中 mode可以爲:
ZipFile.READ 或 ZipFile.DELETE
設定爲ZipFile.DELETE後,會自動刪除文件。
你可以使用:
public Enumeration<? extends ZipEntry> entries( )
來獲得ZipFile的所有條目。
獲得一個ZipEntry:
public ZipEntry getEntry(String name)
額外說明:ZipEntry中的getName的方法將返回這個名字。
獲得對應ZipEntry的輸入流:
public InputStream getInputStream(ZipEntry ze) throws IOException
16 Zip條目
使用構造方法來進行對象拷貝。
拷貝內容將會包含特定的屬性,例如下面這些get方法對應的屬性:
public String getName( )
public long getTime( )
public long getSize( )
public long getCompressedSize( )
public long getCrc( )
public int getMethod( )
public byte[] getExtra( )
public String getComment( )
public boolean isDirectory( )
Java可以使用兩種格式保存zip格式。無壓縮的或者壓縮的。
分別表示爲:
public static final int STORED = ZipEntry.STORED;
public static final int DEFLATED = ZipEntry.DEFLATED;
解壓文件將使用Deflater提供的解壓方法來進行解壓.(
因爲:
public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants
)
由於zip不僅是壓縮形式也是歸檔形式,所以它包含了多個內容項,這些內容項各自包含了壓縮和儲存的文件。
此外,zip文件頭部包含了歸檔本身的信息,例如條目的位置。
因此,不該在輸出流中寫入原始的壓縮的數據。
因此必須創建連續的文件,然後寫入到條目中。
下面幾步:
-
Construct a ZipOutputStream object from an underlying stream, most often a file output stream.
-
Set the comment for the zip file (optional).
-
Set the default compression level and method (optional).
-
Construct a ZipEntry object.
-
Set the metainformation for the zip entry.
-
Put the zip entry in the archive.
-
Write the entry's data onto the output stream.
-
Close the zip entry (optional).
-
Repeat steps 4 through 8 for each entry you want to store in the archive.
-
Finish the zip output stream.
-
Close the zip output stream.
常規的write( ), flush( ), and close( )將導致錯誤。
詳細解釋:
1> 構造和初始化ZipOutputStream:
FileOutputStream fout = new FileOutputStream("data.zip");
ZipOutputStream zout = new ZipOutputStream(fout);
2> 設置評論
public void setComment(String comment)
3> 設置壓縮級別和方法
設置方法:
public void setMethod(int method)
包括:ZipOutputStream.DEFLATED (壓縮); the alternative is ZipOutputStream.STORED (非壓縮).
設置級別:
public void setLevel(int level)
0無壓縮 9最高壓縮
4> 構建ZipEntry並放入
public void putNextEntry(ZipEntry ze) throws IOException
5> 寫入Entry數據
public void write(byte[] data, int offset, int length) throws IOException
6> 關閉Entry
public void closeEntry( ) throws IOException
7> 完成輸出流
public void finish( ) throws IOException
8> 關閉輸出流
public void close( ) throws IOException
例子(截取):
FileOutputStream fout = new FileOutputStream(outputFile);
ZipOutputStream zout = new ZipOutputStream(fout);
zout.setLevel(level);
for (int i = start; i < args.length; i++) {
ZipEntry ze = new ZipEntry(args[i]);
FileInputStream fin = new FileInputStream(args[i]);
try {
System.out.println("Compressing " + args[i]);
zout.putNextEntry(ze);
for (int c = fin.read(); c != -1; c = fin.read( )) {
zout.write(c);
}
}
finally {
fin.close( );
}
}
zout.close( );
從這個例子我們看出:
文件輸入流讀取字符,然後用zip輸出流進行輸出。
所以ZipEntry用於指定輸出的壓縮內容。
大概就是兩步,指定nextEntry ;然後寫入
18 The ZipInputStream Class
和輸出流很相似,也有幾步:
-
Construct a ZipInputStream object from an underlying stream.
-
Open the next zip entry in the archive.
-
Read data from the zip entry using InputStream methods such as read( ).
-
Close the zip entry (optional).
-
Repeat steps 2 through 4 as long as there are more entries (files) remaining in the archive.
-
Close the zip input stream.
樣例:
import java.util.zip.*;
import java.io.*;
public class Unzipper2 {
public static void main(String[] args) throws IOException {
for (int i = 0; i < args.length; i++) {
FileInputStream fin = new FileInputStream(args[i]);
ZipInputStream zin = new ZipInputStream(fin);
ZipEntry ze = null;
while ((ze = zin.getNextEntry( )) != null) {
System.out.println("Unzipping " + ze.getName( ));
FileOutputStream fout = new FileOutputStream(ze.getName( ));
for (int c = zin.read(); c != -1; c = zin.read( )) {
fout.write(c);
}
zin.closeEntry( );
fout.close( );
}
zin.close( );
}
}
}
ZipEntry僅僅用於控制讀取長度。
19 校驗和
在文本文件中1位的修改通常只會影響一個字符,但是在壓縮文件中,1位的錯誤將會導致整個文件無法讀取。
所以在壓縮文件末尾加上校驗和已確保文件是完好無損的。
當使用1位校驗和時,往往不能滿足要求,
如果使用8位,然後對65536求餘,這樣幾乎結果會隨機出現在任意位置,從而有更好的效果
當然,精心設計的校驗和會有更好的效果。
接口:
java.util.zip.Checksum
需要實現的方法:
public abstract void update(int b)
public abstract void update(byte[] data, int offset, int length)
public abstract long getValue( )
public abstract void reset( )
雖然,你可以實現自己的,但是建議使用已有的。
例如:
CRC32 and Adler32,他們都在java.util.zip包中。
除了直接使用這兩個類的update方式來進行更新以外,考慮流:
public CheckedInputStream(InputStream in, Checksum cksum) public CheckedOutputStream(OutputStream out, Checksum cksum)
例子如下:
FileInputStream fin = new FileInputStream("/etc/passwd");
Checksum cksum = new CRC32( );
CheckedInputStream cin = new CheckedInputStream(fin, cksum);