1. 程式人生 > >Android 利用Java實現壓縮與解壓縮(zip、gzip)支援中文路徑

Android 利用Java實現壓縮與解壓縮(zip、gzip)支援中文路徑

  zip扮演著歸檔和壓縮兩個角色;gzip並不將檔案歸檔,僅只是對單個檔案進行壓縮,所以,在UNIX平臺上,命令tar通常用來建立一個檔案檔案,然後命令gzip來將檔案檔案壓縮。


  Java I/O類庫還收錄了一些能讀寫壓縮格式流的類。要想提供壓縮功能,只要把它們包在已有的I/O類的外面就行了。這些類不是Reader和Writer,而是InputStream和OutStreamput的子類。這是因為壓縮演算法是針對byte而不是字元的。


  相關類與介面:
  Checksum 介面:被類Adler32和CRC32實現的介面
  Adler32 :使用Alder32演算法來計算Checksum數目
  CRC32

:使用CRC32演算法來計算Checksum數目
  CheckedInputStream :InputStream派生類,可得到輸入流的校驗和Checksum,用於校驗資料的完整性
  CheckedOutputStream :OutputStream派生類,可得到輸出流的校驗和Checksum, 用於校驗資料的完整性
  DeflaterOutputStream :壓縮類的基類。
  ZipOutputStream :DeflaterOutputStream的一個子類,把資料壓縮成Zip檔案格式。
  GZIPOutputStream :DeflaterOutputStream的一個子類,把資料壓縮成GZip檔案格式
  InflaterInputStream
:解壓縮類的基類
  ZipInputStream :InflaterInputStream的一個子類,能解壓縮Zip格式的資料
  GZIPInputStream :InflaterInputStream的一個子類,能解壓縮Zip格式的資料
  ZipEntry 類:表示 ZIP 檔案條目
  ZipFile 類:此類用於從 ZIP 檔案讀取條目
 

用GZIP進行對單個檔案壓縮


  GZIP的介面比較簡單,因此如果你只需對一個流進行壓縮的話,可以使用它。當然它可以壓縮字元流,與可以壓縮位元組流,下面是一個對GBK編碼格式的文字檔案進行壓縮的。


  壓縮類的用法非常簡單;只要用GZIPOutputStream 或ZipOutputStream把輸出流包起來,再用GZIPInputStream 或ZipInputStream把輸入流包起來就行了。剩下的都是些普通的I/O操作。
  Java程式碼

View Code

 1 import java.io.BufferedOutputStream;
 2 import java.io.BufferedReader;
 3 import java.io.FileInputStream;
 4 import java.io.FileOutputStream;
 5 import java.io.IOException;
 6 import java.io.InputStreamReader;
 7 import java.util.zip.GZIPInputStream;
 8 import java.util.zip.GZIPOutputStream;
 9 public class GZIPcompress {
10   public static void main(String[] args) throws IOException {
11     //做準備壓縮一個字元檔案,注,這裡的字元檔案要是GBK編碼方式的
12     BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("e:/tmp/source.txt"), "GBK"));
13   //使用GZIPOutputStream包裝OutputStream流,使其具體壓縮特性,最後會生成test.txt.gz壓縮包
14   //並且裡面有一個名為test.txt的檔案
15   BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream("test.txt.gz")));
16   System.out.println("開始寫壓縮檔案...");
17   int c;
18   while ((c = in.read()) != -1) {
19   /*
20   * 注,這裡是壓縮一個字元檔案,前面是以字元流來讀的,不能直接存入c,因為c已是Unicode
21   * 碼,這樣會丟掉資訊的(當然本身編碼格式就不對),所以這裡要以GBK來解後再存入。
22   */
23     out.write(String.valueOf((char) c).getBytes("GBK"));
24   }
25   in.close();
26   out.close();
27   System.out.println("開始讀壓縮檔案...");
28   //使用GZIPInputStream包裝InputStream流,使其具有解壓特性
29   BufferedReader in2 = new BufferedReader(new InputStreamReader(
30   new GZIPInputStream(new FileInputStream("test.txt.gz")), "GBK"));
31   String s;
32   //讀取壓縮檔案裡的內容
33   while ((s = in2.readLine()) != null) {
34     System.out.println(s);
35     }
36       in2.close();
37     }
38   }

使用Zip進行多個檔案壓縮
  

  Java對Zip格式類庫支援得比較全面,得用它可以把多個檔案壓縮成一個壓縮包。這個類庫使用的是標準Zip格式,所以能與很多的壓縮工具相容。


  ZipOutputStream類有設定壓縮方法以及在壓縮方式下使用的壓縮級別,zipOutputStream.setMethod(int method)設定用於條目的預設壓縮方法。只要沒有為單個 ZIP 檔案條目指定壓縮方法,就使用ZipOutputStream所設定的壓縮方法來儲存,預設值為 ZipOutputStream.DEFLATED(表示進行壓縮儲存),還可以設定成STORED(表示僅打包歸檔儲存)。
  ZipOutputStream在設定了壓縮方法為DEFLATED後,我們還可以進一步使用setLevel(int level)方法來設定壓縮級別,壓縮級別值為0-9共10個級別(值越大,表示壓縮越利害),預設為 Deflater.DEFAULT_COMPRESSION=-1。當然我們也可以通過條目ZipEntry的setMethod方法為單個條件設定壓縮方法。 
  

  類ZipEntry描述了儲存在ZIP檔案中的壓縮檔案。類中包含有多種方法可以用來設定和獲得ZIP條目的資訊。類ZipEntry是被 ZipFile[zipFile.getInputStream(ZipEntry entry)]和ZipInputStream使用來讀取ZIP檔案,ZipOutputStream來寫入ZIP檔案的。有以下這些有用的方法:getName()返回條目名稱、isDirectory()如果為目錄條目,則返回 true(目錄條目定義為其名稱以 '/' 結尾的條目)、setMethod(int method) 設定條目的壓縮方法,可以為 ZipOutputStream.STORED 或 ZipOutputStream .DEFLATED。


  下面例項我們使用了apache的zip工具包(所在包為ant.jar ),因為java型別自帶的不支援中文路徑,不過兩者使用的方式是一樣的,只是apache壓縮工具多了設定編碼方式的介面,其他基本上是一樣的。另外,如果使用org.apache.tools.zip.ZipOutputStream來壓縮的話,我們只能使用 org.apache.tools.zip.ZipEntry來解壓,而不能使用java.util.zip.ZipInputStream來解壓讀取了,當然apache並未提供ZipInputStream類。
  Java程式碼

 1   import java.io.BufferedInputStream;
 2   import java.io.BufferedOutputStream;
 3   import java.io.File;
 4   import java.io.FileInputStream;
 5   import java.io.FileNotFoundException;
 6   import java.io.FileOutputStream;
 7   import java.io.IOException;
 8   import java.util.Enumeration;
 9   import java.util.zip.CRC32;
10   import java.util.zip.CheckedInputStream;
11   import java.util.zip.CheckedOutputStream;
12   import java.util.zip.Deflater;
13   import java.util.zip.ZipException;
14   import java.util.zip.ZipInputStream;
15   import org.apache.tools.zip.ZipEntry;
16   import org.apache.tools.zip.ZipFile;
17   import org.apache.tools.zip.ZipOutputStream;
18   /**
19   *
20   * 提供對單個檔案與目錄的壓縮,並支援是否需要建立壓縮源目錄、中文路徑
21   *
22   * @author jzj
23   */
24   public class ZipCompress {
25   private static boolean isCreateSrcDir = true;//是否建立源目錄
26   /**
27   * @param args
28   * @throws IOException
29   */
30   public static void main(String[] args) throws IOException {
31     String src = "m:/新建文字文件.txt";//指定壓縮源,可以是目錄或檔案
32     String decompressDir = "e:/tmp/decompress";//解壓路徑
33     String archive = "e:/tmp/test.zip";//壓縮包路徑
34     String comment = "Java Zip 測試.";//壓縮包註釋
35     //----壓縮檔案或目錄
36     writeByApacheZipOutputStream(src, archive, comment);
37     /*
38     * 讀壓縮檔案,註釋掉,因為使用的是apache的壓縮類,所以使用java類庫中
39     * 解壓類時出錯,這裡不能執行
40     */
41     //readByZipInputStream();
42     //----使用apace ZipFile讀取壓縮檔案
43     readByApacheZipFile(archive, decompressDir);
44   }
45   
46
47   public static void writeByApacheZipOutputStream(String src, String archive, String comment) throws FileNotFoundException, IOException {
48     //----壓縮檔案:
49     FileOutputStream f = new FileOutputStream(archive);
50     //使用指定校驗和建立輸出流
51     CheckedOutputStream csum = new CheckedOutputStream(f, new CRC32());
52     ZipOutputStream zos = new ZipOutputStream(csum);
53     //支援中文
54     zos.setEncoding("GBK");
55     BufferedOutputStream out = new BufferedOutputStream(zos);
56     //設定壓縮包註釋
57     zos.setComment(comment);
58     //啟用壓縮
59     zos.setMethod(ZipOutputStream.DEFLATED);
60     //壓縮級別為最強壓縮,但時間要花得多一點
61     zos.setLevel(Deflater.BEST_COMPRESSION);
62     File srcFile = new File(src);
63     if (!srcFile.exists() || (srcFile.isDirectory() && srcFile.list().length == 0)) {
64       throw new FileNotFoundException("File must exist and ZIP file must have at least one entry.");
65     }
66     //獲取壓縮源所在父目錄
67     src = src.replaceAll("\\", "/");
68     String prefixDir = null;
69     if (srcFile.isFile()) {
70       prefixDir = src.substring(0, src.lastIndexOf("/") + 1);
71     } else {
72       prefixDir = (src.replaceAll("/$", "") + "/");
73     }
74     //如果不是根目錄
75     if (prefixDir.indexOf("/") != (prefixDir.length() - 1) && isCreateSrcDir) {
76       prefixDir = prefixDir.replaceAll("[^/]+/$", "");
77     }
78     //開始壓縮
79     writeRecursive(zos, out, srcFile, prefixDir);
80     out.close();
81     // 注:校驗和要在流關閉後才準備,一定要放在流被關閉後使用
82     System.out.println("Checksum: " + csum.getChecksum().getValue());
83     BufferedInputStream bi;
84   }

  1   /**
  2   * 使用 org.apache.tools.zip.ZipFile 解壓檔案,它與 java 類庫中的
  3   * java.util.zip.ZipFile 使用方式是一新的,只不過多了設定編碼方式的
  4   * 介面。
  5   *
  6   * 注,apache 沒有提供 ZipInputStream 類,所以只能使用它提供的ZipFile
  7   * 來讀取壓縮檔案。
  8   * @param archive 壓縮包路徑
  9   * @param decompressDir 解壓路徑
 10   * @throws IOException
 11   * @throws FileNotFoundException
 12   * @throws ZipException
 13   */
 14   public static void readByApacheZipFile(String archive, String decompressDir) throws IOException, FileNotFoundException, ZipException {
 15     BufferedInputStream bi;
 16     ZipFile zf = new ZipFile(archive, "GBK");//支援中文
 17     Enumeration e = zf.getEntries();
 18     while (e.hasMoreElements()) {
 19       ZipEntry ze2 = (ZipEntry) e.nextElement();
 20       String entryName = ze2.getName();
 21       String path = decompressDir + "/" + entryName;
 22       if (ze2.isDirectory()) {
 23         System.out.println("正在建立解壓目錄 - " + entryName);  
 24         File decompressDirFile = new File(path);
 25         if (!decompressDirFile.exists()) {
 26           decompressDirFile.mkdirs();
 27         }
 28       } else {
 29         System.out.println("正在建立解壓檔案 - " + entryName);
 30         String fileDir = path.substring(0, path.lastIndexOf("/"));
 31         File fileDirFile = new File(fileDir);
 32         if (!fileDirFile.exists()) {
 33           fileDirFile.mkdirs();
 34         }
 35         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(decompressDir + "/" + entryName));
 36         bi = new BufferedInputStream(zf.getInputStream(ze2));
 37         byte[] readContent = new byte[1024];
 38         int readCount = bi.read(readContent);
 39         while (readCount != -1) {
 40           bos.write(readContent, 0, readCount);
 41           readCount = bi.read(readContent);
 42         }
 43         bos.close();
 44       }
 45     }
 46     zf.close();
 47   }
 48
 49
 50
 51
 52   /**
 53   * 使用 java api 中的 ZipInputStream 類解壓檔案,但如果壓縮時採用了
 54   * org.apache.tools.zip.ZipOutputStream時,而不是 java 類庫中的
 55   * java.util.zip.ZipOutputStream時,該方法不能使用,原因就是編碼方
 56   * 式不一致導致,執行時會拋如下異常:
 57   * java.lang.IllegalArgumentException
 58   * at java.util.zip.ZipInputStream.getUTF8String(ZipInputStream.java:290)
 59   *
 60   * 當然,如果壓縮包使用的是java類庫的java.util.zip.ZipOutputStream
 61   * 壓縮而成是不會有問題的,但它不支援中文
 62   *
 63   * @param archive 壓縮包路徑
 64   * @param decompressDir 解壓路徑
 65   * @throws FileNotFoundException
 66   * @throws IOException
 67   */
 68   public static void readByZipInputStream(String archive, String decompressDir) throws FileNotFoundException, IOException {
 69     BufferedInputStream bi;
 70     //----解壓檔案(ZIP檔案的解壓縮實質上就是從輸入流中讀取資料):
 71     System.out.println("開始讀壓縮檔案");
 72     FileInputStream fi = new FileInputStream(archive);
 73     CheckedInputStream csumi = new CheckedInputStream(fi, new CRC32());
 74     ZipInputStream in2 = new ZipInputStream(csumi);
 75     bi = new BufferedInputStream(in2);
 76     java.util.zip.ZipEntry ze;//壓縮檔案條目
 77     //遍歷壓縮包中的檔案條目
 78     while ((ze = in2.getNextEntry()) != null) {
 79       String entryName = ze.getName();
 80       if (ze.isDirectory()) {
 81         System.out.println("正在建立解壓目錄 - " + entryName);
 82         File decompressDirFile = new File(decompressDir + "/" + entryName);
 83         if (!decompressDirFile.exists()) {
 84           decompressDirFile.mkdirs();
 85         }
 86       } else {
 87         System.out.println("正在建立解壓檔案 - " + entryName);
 88         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(decompressDir + "/" + entryName));
 89         byte[] buffer = new byte[1024];
 90         int readCount = bi.read(buffer);
 91         while (readCount != -1) {
 92           bos.write(buffer, 0, readCount);
 93           readCount = bi.read(buffer);
 94         }
 95         bos.close();
 96       }
 97     }
 98
 99     bi.close();
100     System.out.println("Checksum: " + csumi.getChecksum().getValue());
101   }
102
103
104
105
106   /**
107   * 遞迴壓縮
108   *
109   * 使用 org.apache.tools.zip.ZipOutputStream 類進行壓縮,它的好處就是支援中文路徑,
110   * 而Java類庫中的 java.util.zip.ZipOutputStream 壓縮中文檔名時壓縮包會出現亂碼。
111   * 使用 apache 中的這個類與 java 類庫中的用法是一新的,只是能設定編碼方式了。
112   *
113   * @param zos
114   * @param bo
115   * @param srcFile
116   * @param prefixDir
117   * @throws IOException
118   * @throws FileNotFoundException
119   */
120   private static void writeRecursive(ZipOutputStream zos,BufferedOutputStream bo,File srcFile, String prefixDir) throws IOException, FileNotFoundException {
121     ZipEntry zipEntry;
122     String filePath = srcFile.getAbsolutePath().replaceAll("\\", "/").replaceAll("//", "/");
123     if (srcFile.isDirectory()) {
124       filePath = filePath.replaceAll("/$", "") + "/";
125     }
126     String entryName = filePath.replace(prefixDir, "").replaceAll("/$", "");
127     if (srcFile.isDirectory()) {
128       if (!"".equals(entryName)) {
129         System.out.println("正在建立目錄 - " + srcFile.getAbsolutePath() + " entry/");
130         zos.putNextEntry(zipEntry);
131       }
132       File srcFiles[] = srcFile.listFiles();
133       for (int i = 0; i < srcFiles.length; i++) {
134         writeRecursive(zos, bo, srcFiles[i], prefixDir);
135       }
136     } else {
137       System.out.println("正在寫檔案 - " + srcFile.getAbsolutePath() + " entrym:/新建文字文件.txt";//指定壓縮源,可以是目錄或檔案
138       String decompressDir = "e:/tmp/decompress";//解壓路徑
139       String archive = "e:/tmp/test.zip";//壓縮包路徑
140       String comment = "Java Zip 測試.";//壓縮包註釋
141       //----壓縮檔案或目錄
142       writeByApacheZipOutputStream(src, archive, comment);
143       /*
144       * 讀壓縮檔案,註釋掉,因為使用的是apache的壓縮類,所以使用java類庫中
145       * 解壓類時出錯,這裡不能執行
146       */
147       //readByZipInputStream();
148       //----使用apace ZipFile讀取壓縮檔案
149       readByApacheZipFile(archive, decompressDir);
150     }
151     public static void writeByApacheZipOutputStream(String src, String archive, String comment) throws FileNotFoundException, IOException {
152     //----壓縮檔案:
153     FileOutputStream f = new FileOutputStream(archive); 
154     //使用指定校驗和建立輸出流
155     CheckedOutputStream csum = new CheckedOutputStream(f, new CRC32());
156     ZipOutputStream zos = new ZipOutputStream(csum);
157     //支援中文
158     zos.setEncoding("GBK");
159     BufferedOutputStream out = new BufferedOutputStream(zos);
160     //設定壓縮包註釋
161     zos.setComment(comment);
162     //啟用壓縮
163     zos.setMethod(ZipOutputStream.DEFLATED);
164     //壓縮級別為最強壓縮,但時間要花得多一點
165     zos.setLevel(Deflater.BEST_COMPRESSION);
166     File srcFile = new File(src);
167     if (!srcFile.exists() || (srcFile.isDirectory() && srcFile.list().length == 0)) {
168       throw new FileNotFoundException("File must exist and ZIP file must have at least one entry.");
169     }
170
171
172     //獲取壓縮源所在父目錄
173     src = src.replaceAll("\\", "/");
174     String prefixDir = null;
175     if (srcFile.isFile()) {
176       prefixDir = src.substring(0, src.lastIndexOf("/") + 1);
177     } else {
178       prefixDir = (src.replaceAll("/$", "") + "/");
179     }
180
181
182     //如果不是根目錄
183     if (prefixDir.indexOf("/") != (prefixDir.length() - 1) && isCreateSrcDir) {
184       prefixDir = prefixDir.replaceAll("[^/]+/$", "");
185     }
186   
187
188     //開始壓縮
189     writeRecursive(zos, out, srcFile, prefixDir);
190     out.close();
191     // 注:校驗和要在流關閉後才準備,一定要放在流被關閉後使用
192     System.out.println("Checksum: " + csum.getChecksum().getValue());
193     BufferedInputStream bi;
194   }


  要想把檔案加入壓縮包,你必須將ZipEntry物件傳給putNextEntry( )。ZipEntry是一個介面很複雜的物件,它能讓你設定和讀取Zip檔案裡的某條記錄的資訊,這些資訊包括:檔名,壓縮前和壓縮後的大小,日期,CRC校驗碼,附加欄位,註釋,壓縮方法,是否是目錄。雖然標準的Zip格式是支援口令的,但是Java的Zip類庫卻不支援。而且ZipEntry 卻只提供了CRC的介面,而CheckedInputStream和CheckedOutputStream卻支援Adler32和CRC32兩種校驗碼。雖然這是底層的Zip格式的限制,但卻妨礙了你使用更快的Adler32了。


  要想提取檔案,可以用ZipInputStream的getNextEntry( )方法。只要壓縮包裡還有ZipEntry,它就會把它提取出來。此外還有一個更簡潔的辦法,你可以用ZipFile物件去讀檔案。ZipFile有一個 entries()方法,它可以返回ZipEntries的Enumeration。然後通過zipFile. getInputStream(ZipEntry entry)獲取壓縮流就可以讀取相應條目了。


  要想讀取校驗碼,必須先獲取Checksum物件。我們這裡用的是CheckedOutputStream和 CheckedInputStream,不過你也可以使用Checksum。java.util.zip包中比較重要校驗演算法類是Adler32和 CRC32,它們實現了java.util.zip.Checksum介面,並估算了壓縮資料的校驗和(checksum)。在運算速度方面,Adler32演算法比CRC32演算法要有一定的優勢;但在資料可信度方面,CRC32演算法則要更勝一籌。GetValue方法可以用來獲得當前的 checksum值,reset方法能夠重新設定checksum為其預設的值。


  校驗和一般用來校驗檔案和資訊是否正確的傳送。舉個例子,假設你想建立一個ZIP檔案,然後將其傳送到遠端計算機上。當到達遠端計算機後,你就可以使用checksum檢驗在傳輸過程中檔案是否發生錯誤,有點像下載檔案後我們可以使用雜湊值來校驗檔案下載過程是否出錯了。


  Zip類裡還有一個讓人莫名其妙的setComment( )方法。如ZipCompress.java所示,寫檔案的時候,你可以加註釋,但是讀檔案的時候,ZipInputSream卻不提供介面。看來它的註釋功能完全是針對條目的,是用ZipEntry實現的。


  當然,GZIP和Zip不光能用來壓縮檔案——它還能壓縮任何東西,包括要通過網路傳輸的資料。