使用GZIP和Zip压缩Java数据流
轉載自? ?使用GZIP和Zip壓縮Java數據流
本文通過對數據壓縮算法的簡要介紹,然后以詳細的示例演示了利用java.util.zip包實現數據的壓縮與解壓,并擴展到在網絡傳輸方面如何應用java.util.zip包現數據壓縮與解壓綜述
許多信息資料都或多或少的包含一些多余的數據。通常會導致在客戶端與服務器之間,應用程序與計算機之間極大的數據傳輸量。最常見的解決數據存儲和信 息傳送的方法是安裝額外的存儲設備和擴展現有的通訊能力。這樣做是可以的,但無疑會增加組織的運作成本。一種有效的解決數據存儲與信息傳輸的方法是通過更 有效率的代碼來存儲數據。這篇文章簡要的介紹了數據的壓縮與解壓縮,并展示了用java.util.zip包來實現數據的壓縮與解壓縮是多么的方便與高 效。
當然用諸如WinZip,gzip,和Java壓縮(或jar)之類的工具也可以實現數據的壓縮與解壓縮,這些工具都是獨立的應用程序。你也可以在 JAVA應用程序中調用這些工具,但這并不是最直接的方法,也不是有效的解決方法。尤其是你想更快速地實現數據的壓縮與解壓縮(例如在傳輸數據到遠程機器 之前)。這篇文章包括以下內容:
- 給出一個關于數據壓縮的簡單的介紹
- 描述java.util.zip包
- 示例如何使用該包實現數據的壓縮與解壓縮
- 示例如何壓縮串行化的對象并將其存儲在磁碟上
- 示例如何通過數據壓縮來增強"客戶/服務"應用程序的性能
數據壓縮概述
文件中數據冗余的最簡單的類型是"字符的復制"。讓我們先來看下面一個字符串:
JJJJJJAAAAVVVVAAAAAA 這個字符串可以用更簡潔的方式來編碼,那就是通過替換每一個重復的字符串為單個的實例字符加上記錄重復次數的數字來表示,上面的字符串可以被編碼為下面的形式:
6J4A4V6A 在這里,"6J"意味著6個字符J,"4A"意味著4個字符A,以此類推。這種字符串壓縮方式稱為"行程長度編碼"方式,簡稱RLE。
再舉一個例子,考慮一下矩形圖像的存儲。一個單色位圖,可以被存儲為下面這種形式,如圖1所示。
另外一種方式是將圖像存為一個圖元文件:
Rectangle 11, 3, 20, 5
上面的表示方法是講矩形的起始坐標是(11,3),寬度是20,高度是5。
上述的矩形圖像可以使用RLE編碼方式壓縮,通過對相同位記數表示如下:
0, 40
0, 40
0,10 1,20 0,10
0,10 1,1 0,18 1,1 0,10
0,10 1,1 0,18 1,1 0,10
0,10 1,1 0,18 1,1 0,10
0,10 1,20 0,10
0,40
上面第一行是講圖像的第一行由40個0組成。第三行是講圖像的第三行是由10個0加上20個1再加上10個0組成,其它行以此類推。
大家注意,RLE方法需要將其表示的文件與編碼文件分開。所以,這種方法不能應用于所有的文件。其它的壓縮技術包括變長編碼(也被稱為哈夫曼編碼),還有其它的方法。要想了解更詳細的信息,請參考有關數據和圖像壓縮技術方面的圖書,一定會有收獲的。
數據壓縮有很多益處。不管怎么說,最主要的好處就是減少存儲方面的需求。同樣的,對于數據通信來講,壓縮數據在媒體中的將導致信息傳輸數據的提升。 數據的壓縮能夠通過軟件在現有的硬件設備上實現或者通過帶有壓縮技術的特殊的硬件設備來實現。圖表2顯示了基本的數據壓縮結構圖。
ZIP VS GZIP
如果你是在Windows系統下工作,你可能會對工具WinZip很熟悉,是用來創建壓縮檔案和解開壓縮檔案的。而在UNIX平臺上,會有一些不同,命令tar用來創建一個檔案文件(并不壓縮),其它的程序(gzip或compress)用來創建一個壓縮檔案。
WinZip和PkZip之類的工具同時扮演著歸檔和壓縮兩個角色。他們將文件壓縮并將其歸檔。另一方面,gzip并不將文件歸檔。所以,在UNIX平臺上,命令tar通常用來創建一個檔案文件,然后命令gzip來將檔案文件壓縮。
Java.util.zip包
Java提供了java.util.zip包用來兼容ZIP格式的數據壓縮。它提供了一系列的類用來讀取,創建,修改ZIP和GZIP格式的文件。 它還提供了工具類來計算任意輸入流的數目,這可以用來驗證輸入數據的有效性。該包提供了一個接口,十四個類,和兩個異常處理類,如表1所示。
| 條目 | 類型 | 描述 |
| Checksum | 接口 | 被類Adler32和CRC32實現的接口 |
| Adler32 | 類 | 使用Alder32算法來計算Checksum數目 |
| CheckedInputStream | 類 | 一個輸入流,保存著被讀取數據的Checksum |
| CheckedOutputStream | 類 | 一個輸出流,保存著被讀取數據的Checksum |
| CRC32 | 類 | 使用CRC32算法來計算Checksum數目 |
| Deflater | 類 | 使用ZLIB壓縮類,支持通常的壓縮方式 |
| DeflaterOutputStream | 類 | 一個輸出過濾流,用來壓縮Deflater格式數據 |
| GZIPInputStream | 類 | 一個輸入過濾流,讀取GZIP格式壓縮數據 |
| GZIPOutputStream | 類 | 一個輸出過濾流,讀取GZIP格式壓縮數據 |
| Inflater | 類 | 使用ZLIB壓縮類,支持通常的解壓方式 |
| InlfaterInputStream | 類 | 一個輸入過濾流,用來解壓Inlfater格式的壓縮數據 |
| ZipEntry | 類 | 存儲ZIP條目 |
| ZipFile | 類 | 從ZIP文件中讀取ZIP條目 |
| ZipInputStream | 類 | 一個輸入過濾流,用來讀取ZIP格式文件中的文件 |
| ZipOutputStream | 類 | 一個輸出過濾流,用來向ZIP格式文件口寫入文件 |
| DataFormatException | 異常類 | 拋出一個數據格式錯誤 |
| ZipException | 異常類 | 拋出一個ZIP文件 |
注意:ZLIB壓縮類最初是作為可移植的網絡圖像文件格式(PNG)標準的一部分開發的,是不受專利保護的。
從ZIP文件中解壓縮和提取數據
java.util.zip包提供了數據壓縮與解壓縮所需要的類。ZIP文件的解壓縮實質上就是從輸入流中讀取數據。Java.util.zip包 提供了類ZipInputStream來讀取ZIP文件。ZipInputStream流的創建與其它輸入流的創建沒什么兩樣。舉個例子,下面的代碼段創 建了一個輸入流來讀取ZIP格式的文件:
| FileInputStream fis = new FileInputStream("figs.zip"); ZipInputStream zin = new ZipInputStream(new BufferedInputStream(fis)); |
ZIP輸入流打開后,你可以使用getNextEntry方法來讀取ZIP文件中的條目數,該方法返回一個ZipEntry對象。如果到達文件的尾部,getNextEntry返回null:
| ZipEntry entry; while((entry = zin.getNextEntry()) != null) {// extract data// open output streams } |
現在,你應該建立一個輸出流,如下所示:
| int BUFFER = 2048; FileOutputStream fos = new FileOutputStream(entry.getName()); BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER); |
注意:在這段代碼中我們用BufferedOutputStream代替了ZIPOutputStream。 ZIPOutputStream和GZIPOutputStream使用內置的512字節緩沖。當緩沖區的大小大于512字節時,使用 BufferedOutputStream才是正確的(例子中設置為2048)。ZIPOutputStream不允許你設置緩沖區的大 小,GZIPOutputStream也是一樣,但創建 GZIPOutputStream 對象時可以通過構造函數的參數指定內置的緩沖尺寸。
這段代碼中,使用ZIP內含的條目名稱創建一個文件輸出流。可以使用entry.getName來得到它的返回句柄。接著讀出被壓縮的源數據,然后寫入輸出流:
| while ((count = zin.read(data, 0, BUFFER)) != -1) {//System.out.write(x);dest.write(data, 0, count); } |
最后,不要忘記關閉輸入和輸出流:
| dest.flush(); dest.close(); zin.close(); |
例程1的源程序UnZip.java顯示如何解壓縮并從ZIP檔案中將文件釋放出來。測試這個例子,編譯這個類,并運行它,傳給它一個ZIP格式的文件作為參數:
prompt> java UnZip somefile.zip
注意:somefile.zip應該是一個ZIP壓縮檔案,可以用任何一種ZIP壓縮工具來創建,例如WinZip。
| UnZip.java import java.io.*; import java.util.zip.*; public class UnZip {static final int BUFFER = 2048;public static void main (String argv[]) {try {BufferedOutputStream dest = null;FileInputStream fis = new FileInputStream(argv[0]);ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));ZipEntry entry;while((entry = zis.getNextEntry()) != null) {System.out.println("Extracting: " +entry);int count;byte data[] = new byte[BUFFER];// write the files to the diskFileOutputStream fos = new FileOutputStream(entry.getName());dest = new BufferedOutputStream(fos, BUFFER);while ((count = zis.read(data, 0, BUFFER)) != -1) {dest.write(data, 0, count);}dest.flush();dest.close();}zis.close();} catch(Exception e) {e.printStackTrace();}} } |
有一點值得大家注意,類ZipInputStream讀出ZIP文件序列(簡單地說就是讀出這個ZIP文件壓縮了多少文件),而類ZipFile使用內嵌的隨機文件訪問機制讀出其中的文件內容,所以不必順序的讀出ZIP壓縮文件序列。
注意:ZIPInputStream和ZipFile之間另外一個基本的不同點在于高速緩沖的使用方面。當文件 使用ZipInputStream和FileInputStream流讀出的時候,ZIP條目不使用高速緩沖。然而,如果使用ZipFile(文件名)來 打開文件,它將使用內嵌的高速緩沖,所以如果ZipFile(文件名)被重復調用的話,文件只被打開一次。緩沖值在第二次打開進使用。如果你工作在 UNIX系統下,這是什么作用都沒有的,因為使用ZipFile打開的所有ZIP文件都在內存中存在映射,所以使用ZipFile的性能優于 ZipInputStream。然而,如果同一ZIP文件的內容在程序執行期間經常改變,或是重載的話,使用ZipInputStream就成為你的首選 了。
下面顯示了使用類ZipFile來解壓一個ZIP文件的過程:
通過指定一個被讀取的ZIP文件,或者是文件名,或者是一個文件對象來創建一個ZipFile對象: ZipFile zipfile = new ZipFile("figs.zip");
使 用entries方法,返回一個枚舉對象,循環獲得文件的ZIP條目對象: while(e.hasMoreElements()) { entry = (ZipEntry) e.nextElement(); // read contents and save them }
ZIP 條目作為參數傳遞給getInputStream方法,可以讀取ZIP文件中指定條目的內容,能過其返回的輸入流(InputStram)對象可以方便的 讀出ZIP條目的內容: is = new BufferedInputStream(zipfile.getInputStream(entry));
獲 取ZIP條目的文件名,創建輸出流,并保存: byte data[] = new byte[BUFFER]; FileOutputStream fos = new FileOutputStream(entry.getName()); dest = new BufferedOutputStream(fos, BUFFER); while ((count = is.read(data, 0, BUFFER)) != -1) { dest.write(data, 0, count); }
最后關閉所有的輸入輸出流 dest.flush(); dest.close(); is.close();
完整的程序代碼如例程2所示。再次編譯這個文件,并傳遞一個ZIP格式的文件做為參數:
prompt> java UnZip2 somefile.zip
將數據壓縮歸檔入一ZIP文件
類ZipOutputStream能夠用來將數據壓縮成一個ZIP文件。ZipOutputStream將數據寫入ZIP格式的輸出流。下面的步驟與創建一個ZIP文件相關。
1、第一步是創建一個ZipOutputStream對象,我們將要寫入輸出流的文件作為參數傳給它。下面的代碼演示了如何創建一個名為"myfigs.zip"的ZIP文件。
FileOutputStream dest = new
FileOutputStream("myfigs.zip");
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
2、一但目標輸出流創建后,下一步就是打開數據源文件。在這個例子中,源數據文件是指那些當前目錄下的文件。命令list用來得到當前目錄下文件列表:
| File f = new File("."); String files[] = f.list(); for (int i=0; i < files.length; i++) {System.out.println("Adding: "+files[i]);FileInputStream fi = new FileInputStream(files[i]);// create zip entry// add entries to ZIP file } |
注意:這個例程能夠壓縮當前目錄下的所有文件。它不能處理子目錄。作為一個練習,你可以修改例程3來處理子目錄。
3、 為讀出的數據創建一個ZIP條目列表:
ZipEntry entry = new ZipEntry(files[i]))
4、 在你將數據寫入ZIP輸出流之前,你必須使用putNextEntry方法將ZIP條目列表寫入輸出流:
out.putNextEntry(entry);
5、 將數據寫入ZIP文件:
int count;
while((count = origin.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, count);
}
6、 最后關閉所有的輸入輸出流:
origin.close();
out.close();
完整的程序代碼如例程3所示。
| Zip.java import java.io.*; import java.util.zip.*; public class Zip {static final int BUFFER = 2048;public static void main (String argv[]) {try {BufferedInputStream origin = null;FileOutputStream dest = new FileOutputStream("c:\\zip\\myfigs.zip");ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));//out.setMethod(ZipOutputStream.DEFLATED);byte data[] = new byte[BUFFER];// get a list of files from current directoryFile f = new File(".");String files[] = f.list();for (int i=0; i < files.length; i++) {System.out.println("Adding: "+files[i]);FileInputStream fi = new FileInputStream(files[i]);origin = new BufferedInputStream(fi, BUFFER);ZipEntry entry = new ZipEntry(files[i]);out.putNextEntry(entry);int count;while((count = origin.read(data, 0, BUFFER)) != -1) {out.write(data, 0, count);}origin.close();}out.close();} catch(Exception e) {e.printStackTrace();}} } |
注意: 條目列表可以以兩種方式加入ZIP文件中,一種是壓縮方式(DEFLATED),另一種是不壓縮方式(STORED),系統默認的存儲方式為壓縮方式 (DEFLATED)。SetMethod方法可以用來設置它的存儲方式。例如,設置存儲方式為DEFLATED(壓縮)應該這樣做: out.setMethod(ZipOutputStream.DEFLATED) 設置存儲方式為(不壓縮)應該這樣做: out.setMethod(ZipOutputStream.STORED)。
ZIP文件屬性
類ZipEntry描述了存儲在ZIP文件中的壓縮文件。類中包含有多種方法可以用來設置和獲得ZIP條目的信息。類ZipEntry是被 ZipFile和ZipInputStream使用來讀取ZIP文件,ZipOutputStream來寫入ZIP文件的。ZipEntry中最有用的一 些方法顯示在下面的表格2中,并且有相應的描述。
| 方法簽名 | 描述 |
| public String getComment() | 返回條目的注釋, 沒有返回null |
| public long getCompressedSize() | 返回條目壓縮后的大小, 未知返回-1 |
| public int getMethod() | 返回條目的壓縮方式,沒有指定返回 -1 |
| public String getName() | 返回條目的名稱 |
| public long getSize() | 返回未被壓縮的條目的大小,未知返回-1 |
| public long getTime() | 返回條目的修改時間, 沒有指定返回-1 |
| public void setComment(String c) | 設置條目的注釋 |
| public void setMethod(int method) | 設置條目的壓縮方式 |
| public void setSize(long size) | 設置沒有壓縮的條目的大小 |
| public void setTime(long time) | 設置條目的修改時間 |
求和校驗
java.util.zip包中另外一些比較重要的類是Adler32和CRC32,它們實現了java.util.zip.Checksum接 口,并估算了壓縮數據的校驗和(checksum)。眾所周知,在運算速度方面,Adler32算法比CRC32算法要有一定的優勢;但在數據可信度方 面,CRC32算法則要更勝一籌。正所謂,"魚與熊掌,不可兼得。",大家只好在不同的場合下,加以取舍了。GetValue方法可以用來獲得當前的 checksum值,reset方法能夠重新設置checksum為其缺省的值。
求和校驗一般用來校驗文件和信息是否正確的傳送。舉個例子,假設你想創建一個ZIP文件,然后將其傳送到遠程計算機上。當到達遠程計算機后,你就可 以使用checksum檢驗在傳輸過程中文件是否發生錯誤。為了演示如何創建checksums,我們修改了例程1和例程3,在例程4和例程5中使用了兩 個新類,一個是CheckedInputStream,另一個是CheckedOutputStream。(大家注意:這兩段代碼在壓縮與解壓縮過程中, 使用了同一種算法,求數據的checksum值。)
| Zip.java import java.io.*; import java.util.zip.*; public class Zip {static final int BUFFER = 2048;public static void main (String argv[]) {try {BufferedInputStream origin = null;FileOutputStream dest = new FileOutputStream("c:\\zip\\myfigs.zip");CheckedOutputStream checksum = new CheckedOutputStream(dest, new Adler32());ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(checksum));//out.setMethod(ZipOutputStream.DEFLATED);byte data[] = new byte[BUFFER];// get a list of files from current directoryFile f = new File(".");String files[] = f.list();for (int i=0; i < files.length; i++) {System.out.println("Adding: "+files[i]);FileInputStream fi = new FileInputStream(files[i]);origin = new BufferedInputStream(fi, BUFFER);ZipEntry entry = new ZipEntry(files[i]);out.putNextEntry(entry);int count;while((count = origin.read(data, 0, BUFFER)) != -1) {out.write(data, 0, count);}origin.close();}out.close();System.out.println("checksum: "+checksum.getChecksum().getValue());} catch(Exception e) {e.printStackTrace();}} } |
| UnZip.java import java.io.*; import java.util.zip.*; public class UnZip {public static void main (String argv[]) {try {final int BUFFER = 2048;BufferedOutputStream dest = null;FileInputStream fis = new FileInputStream(argv[0]);CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32());ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum));ZipEntry entry;while((entry = zis.getNextEntry()) != null) {System.out.println("Extracting: " +entry);int count;byte data[] = new byte[BUFFER];// write the files to the diskFileOutputStream fos = new FileOutputStream(entry.getName());dest = new BufferedOutputStream(fos, BUFFER);while ((count = zis.read(data, 0, BUFFER)) != -1) {dest.write(data, 0, count);}dest.flush();dest.close();}zis.close();System.out.println("Checksum: "+checksum.getChecksum().getValue());} catch(Exception e) {e.printStackTrace();}} } |
測試例程4和5,編譯類文件并運行類Zip來創建一個壓縮檔案(程序會計算出checksum值并顯示在屏幕上),然后運行UnZip類來解壓縮這 個檔案(屏幕上同樣會打印出一個checksum值)。兩個值必須完全相同,否則說明出錯了。Checksums在數據校驗方面非常有用。例如,你可以創 建一個ZIP文件,然后連同checksum值一同傳遞給你的朋友。你的朋友解壓縮文件后,將生成的checksum值與你提供的作一比較,如果相同則說 明在傳遞過程中沒有發生錯誤。
壓縮對象
我們已經看到如何將文件中的數據壓縮并將其歸檔。但如果你想壓縮的數據不在文件中時,應該怎么辦呢?假設有這樣一個例子,你通過套接字 (socket)來傳遞一個大對象。為了提高應用程序的性能,你可能在通過網絡開始傳遞前將數據壓縮,然后在目的地將其解壓縮。另外一個例子,我們假設你 想將一個對象用壓縮格式存儲在磁碟上,ZIP格式是基于記錄方式的,不適合這項工作。GZIP更適合用來實現這種對單一數據流的操作。現在,我們來示例一 下,如果在寫入磁碟前將數據壓縮,并在讀出時將數據解壓縮。示例程序6是一個在單一JVM(java虛擬機)實現了Serializable接口的簡單 類,我們想要串行化該類的實例。
| Employee.java import java.io.*; public class Employee implements Serializable {String name;int age;int salary;public Employee(String name, int age, int salary) {this.name = name;this.age = age;this.salary = salary;}public void print() {System.out.println("Record for: "+name);System.out.println("Name: "+name);System.out.println("Age: "+age);System.out.println("Salary: "+salary);} } |
現在,寫另外一個類來創建兩個從Employee類實例化而來的對象。示例程序7從Employee類創建了兩個對象(sarah和sam)。然后將它們的狀態以壓縮的格式存儲在一個文件中。
| SaveEmployee.java import java.io.*; import java.util.zip.*; public class SaveEmployee {public static void main(String argv[]) throws Exception {// create some objectsEmployee sarah = new Employee("S. Jordan", 28, 56000);Employee sam = new Employee("S. McDonald", 29, 58000);// serialize the objects sarah and samFileOutputStream fos = new FileOutputStream("db");GZIPOutputStream gz = new GZIPOutputStream(fos);ObjectOutputStream oos = new ObjectOutputStream(gz);oos.writeObject(sarah);oos.writeObject(sam);oos.flush();oos.close();fos.close();} } |
現在,示例程序8中的ReadEmpolyee類是用來重新構建兩個對象的狀態。一但構建成功,就調用print方法將其打印出來。
| ReadEmployee.java import java.io.*; import java.util.zip.*; public class ReadEmployee {public static void main(String argv[]) throws Exception{//deserialize objects sarah and samFileInputStream fis = new FileInputStream("db");GZIPInputStream gs = new GZIPInputStream(fis);ObjectInputStream ois = new ObjectInputStream(gs);Employee sarah = (Employee) ois.readObject();Employee sam = (Employee) ois.readObject();//print the records after reconstruction of statesarah.print();sam.print();ois.close();fis.close();} } |
同樣的思想可以用于在網絡間通過(socket)傳輸的大對象。下面的代碼段示例了如何在客戶/服務器之間實現大對象的壓縮:
| // write to client GZIPOutputStream gzipout = new GZIPOutputStream(socket.getOutputStream()); ObjectOutputStream oos = new ObjectOutputStream(gzipout); oos.writeObject(obj); gzipos.finish(); |
下面的代碼段顯示了客戶端從服務器端接收到數據后,如何將其解壓:
| // read from server Socket socket = new Socket(remoteServerIP, PORT); GZIPInputStream gzipin = new GZIPInputStream(socket.getInputStream()); ObjectInputStream ois = new ObjectInputStream(gzipin); Object o = ois.readObject(); |
如何對JAR文件進行操作呢?
Java檔案文件(JAR)格式是基于標準的ZIP文件格式,并附有可選擇的文件清單列表。如果你想要在你我的應用程序中創建JAR文件或從JAR 文件中解壓縮文件,可以使用java.util.jar包,它提供了讀寫JAR文件的類。使用java.util.jar包提供的類與本文所講述的 java.util.zip包十分相似。所以你應該能夠重新編寫本文的源代碼,如果你想使用java.util.jar包的話。
結束語
本文討論了你可以在應用程序中使用的數據壓縮與解壓的應用程序接口,本文的示例程序演示了如何使用java.util.zip包來壓縮數據與解壓縮數據。現在你可以利用這個工具在你的應用程序中實現數據的壓縮與解壓了。
本文也說明了如何在絡傳輸中實現數據的壓縮與解壓縮,以減少網絡阻塞和增強你的客戶/服務器模式應用程序的性能。在網絡傳輸中實現數據的壓縮,只有當傳輸的數據量達到成百上千字節時,你才會感覺到程序性能的提升,如果僅僅是傳遞一個字符串對象,對應用程序是沒什么影響的。
總結
以上是生活随笔為你收集整理的使用GZIP和Zip压缩Java数据流的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 缠绵游戏的歌词 缠绵游戏的歌词简单将介绍
- 下一篇: Java:对double值进行四舍五入,