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

使用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類。

 

  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.  
  16. import org.apache.tools.zip.ZipEntry; 
  17. import org.apache.tools.zip.ZipFile; 
  18. import org.apache.tools.zip.ZipOutputStream; 
  19.  
  20. /** 
  21.  *  
  22.  * 提供對單個文件與目錄的壓縮,並支持是否需要創建壓縮源目錄、中文路徑 
  23.  *  
  24.  * @author jzj 
  25.  */ 
  26. public class ZipCompress { 
  27.  
  28.     private static boolean isCreateSrcDir = true;//是否創建源目錄 
  29.  
  30.     /** 
  31.      * @param args 
  32.      * @throws IOException 
  33.      */ 
  34.     public static void main(String[] args) throws IOException { 
  35.         String src = "m:/新建文本文檔.txt";//指定壓縮源,可以是目錄或文件 
  36.         String decompressDir = "e:/tmp/decompress";//解壓路徑 
  37.         String archive = "e:/tmp/test.zip";//壓縮包路徑 
  38.         String comment = "Java Zip 測試.";//壓縮包註釋 
  39.  
  40.         //----壓縮文件或目錄 
  41.         writeByApacheZipOutputStream(src, archive, comment); 
  42.  
  43.         /* 
  44.          * 讀壓縮文件,註釋掉,因爲使用的是apache的壓縮類,所以使用java類庫中 
  45.          * 解壓類時出錯,這裏不能運行 
  46.          */ 
  47.         //readByZipInputStream(); 
  48.         //----使用apace ZipFile讀取壓縮文件 
  49.         readByApacheZipFile(archive, decompressDir); 
  50.     } 
  51.  
  52.     public static void writeByApacheZipOutputStream(String src, String archive, 
  53.             String comment) throws FileNotFoundException, IOException { 
  54.         //----壓縮文件: 
  55.         FileOutputStream f = new FileOutputStream(archive); 
  56.         //使用指定校驗和創建輸出流 
  57.         CheckedOutputStream csum = new CheckedOutputStream(f, new CRC32()); 
  58.  
  59.         ZipOutputStream zos = new ZipOutputStream(csum); 
  60.         //支持中文 
  61.         zos.setEncoding("GBK"); 
  62.         BufferedOutputStream out = new BufferedOutputStream(zos); 
  63.         //設置壓縮包註釋 
  64.         zos.setComment(comment); 
  65.         //啓用壓縮 
  66.         zos.setMethod(ZipOutputStream.DEFLATED); 
  67.         //壓縮級別爲最強壓縮,但時間要花得多一點 
  68.         zos.setLevel(Deflater.BEST_COMPRESSION); 
  69.  
  70.         File srcFile = new File(src); 
  71.  
  72.         if (!srcFile.exists() || (srcFile.isDirectory() && srcFile.list().length == 0)) { 
  73.             throw new FileNotFoundException( 
  74.                     "File must exist and  ZIP file must have at least one entry."); 
  75.         } 
  76.         //獲取壓縮源所在父目錄 
  77.         src = src.replaceAll("\\\\", "/"); 
  78.         String prefixDir = null
  79.         if (srcFile.isFile()) { 
  80.             prefixDir = src.substring(0, src.lastIndexOf("/") + 1); 
  81.         } else { 
  82.             prefixDir = (src.replaceAll("/$""") + "/"); 
  83.         } 
  84.  
  85.         //如果不是根目錄 
  86.         if (prefixDir.indexOf("/") != (prefixDir.length() - 1) && isCreateSrcDir) { 
  87.             prefixDir = prefixDir.replaceAll("[^/]+/$"""); 
  88.         } 
  89.  
  90.         //開始壓縮 
  91.         writeRecursive(zos, out, srcFile, prefixDir); 
  92.  
  93.         out.close(); 
  94.         // 注:校驗和要在流關閉後才準備,一定要放在流被關閉後使用 
  95.         System.out.println("Checksum: " + csum.getChecksum().getValue()); 
  96.         BufferedInputStream bi; 
  97.     } 
  98.  
  99.     /** 
  100.      * 使用 org.apache.tools.zip.ZipFile 解壓文件,它與 java 類庫中的 
  101.      * java.util.zip.ZipFile 使用方式是一新的,只不過多了設置編碼方式的 
  102.      * 接口。 
  103.      *  
  104.      * 注,apache 沒有提供 ZipInputStream 類,所以只能使用它提供的ZipFile 
  105.      * 來讀取壓縮文件。 
  106.      * @param archive 壓縮包路徑 
  107.      * @param decompressDir 解壓路徑 
  108.      * @throws IOException 
  109.      * @throws FileNotFoundException 
  110.      * @throws ZipException 
  111.      */ 
  112.     public static void readByApacheZipFile(String archive, String decompressDir) 
  113.             throws IOException, FileNotFoundException, ZipException { 
  114.         BufferedInputStream bi; 
  115.  
  116.         ZipFile zf = new ZipFile(archive, "GBK");//支持中文 
  117.  
  118.         Enumeration e = zf.getEntries(); 
  119.         while (e.hasMoreElements()) { 
  120.             ZipEntry ze2 = (ZipEntry) e.nextElement(); 
  121.             String entryName = ze2.getName(); 
  122.             String path = decompressDir + "/" + entryName; 
  123.             if (ze2.isDirectory()) { 
  124.                 System.out.println("正在創建解壓目錄 - " + entryName); 
  125.                 File decompressDirFile = new File(path); 
  126.                 if (!decompressDirFile.exists()) { 
  127.                     decompressDirFile.mkdirs(); 
  128.                 } 
  129.             } else { 
  130.                 System.out.println("正在創建解壓文件 - " + entryName); 
  131.                 String fileDir = path.substring(0, path.lastIndexOf("/")); 
  132.                 File fileDirFile = new File(fileDir); 
  133.                 if (!fileDirFile.exists()) { 
  134.                     fileDirFile.mkdirs(); 
  135.                 } 
  136.                 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream( 
  137.                         decompressDir + "/" + entryName)); 
  138.  
  139.                 bi = new BufferedInputStream(zf.getInputStream(ze2)); 
  140.                 byte[] readContent = new byte[1024]; 
  141.                 int readCount = bi.read(readContent); 
  142.                 while (readCount != -1) { 
  143.                     bos.write(readContent, 0, readCount); 
  144.                     readCount = bi.read(readContent); 
  145.                 } 
  146.                 bos.close(); 
  147.             } 
  148.         } 
  149.         zf.close(); 
  150.     } 
  151.  
  152.     /** 
  153.      * 使用 java api 中的 ZipInputStream 類解壓文件,但如果壓縮時採用了 
  154.      * org.apache.tools.zip.ZipOutputStream時,而不是 java 類庫中的 
  155.      * java.util.zip.ZipOutputStream時,該方法不能使用,原因就是編碼方 
  156.      * 式不一致導致,運行時會拋如下異常: 
  157.      * java.lang.IllegalArgumentException 
  158.      * at java.util.zip.ZipInputStream.getUTF8String(ZipInputStream.java:290) 
  159.      *  
  160.      * 當然,如果壓縮包使用的是java類庫的java.util.zip.ZipOutputStream 
  161.      * 壓縮而成是不會有問題的,但它不支持中文 
  162.      *  
  163.      * @param archive 壓縮包路徑 
  164.      * @param decompressDir 解壓路徑 
  165.      * @throws FileNotFoundException 
  166.      * @throws IOException 
  167.      */ 
  168.     public static void readByZipInputStream(String archive, String decompressDir) 
  169.             throws FileNotFoundException, IOException { 
  170.         BufferedInputStream bi; 
  171.         //----解壓文件(ZIP文件的解壓縮實質上就是從輸入流中讀取數據): 
  172.         System.out.println("開始讀壓縮文件"); 
  173.  
  174.         FileInputStream fi = new FileInputStream(archive); 
  175.         CheckedInputStream csumi = new CheckedInputStream(fi, new CRC32()); 
  176.         ZipInputStream in2 = new ZipInputStream(csumi); 
  177.         bi = new BufferedInputStream(in2); 
  178.         java.util.zip.ZipEntry ze;//壓縮文件條目 
  179.         //遍歷壓縮包中的文件條目 
  180.         while ((ze = in2.getNextEntry()) != null) { 
  181.             String entryName = ze.getName(); 
  182.             if (ze.isDirectory()) { 
  183.                 System.out.println("正在創建解壓目錄 - " + entryName); 
  184.                 File decompressDirFile = new File(decompressDir + "/" + entryName); 
  185.                 if (!decompressDirFile.exists()) { 
  186.                     decompressDirFile.mkdirs(); 
  187.                 } 
  188.             } else { 
  189.                 System.out.println("正在創建解壓文件 - " + entryName); 
  190.                 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream( 
  191.                         decompressDir + "/" + entryName)); 
  192.                 byte[] buffer = new byte[1024]; 
  193.                 int readCount = bi.read(buffer); 
  194.  
  195.                 while (readCount != -1) { 
  196.                     bos.write(buffer, 0, readCount); 
  197.                     readCount = bi.read(buffer); 
  198.                 } 
  199.                 bos.close(); 
  200.             } 
  201.         } 
  202.         bi.close(); 
  203.         System.out.println("Checksum: " + csumi.getChecksum().getValue()); 
  204.     } 
  205.  
  206.     /** 
  207.      * 遞歸壓縮 
  208.      *  
  209.      * 使用 org.apache.tools.zip.ZipOutputStream 類進行壓縮,它的好處就是支持中文路徑, 
  210.      * 而Java類庫中的 java.util.zip.ZipOutputStream 壓縮中文文件名時壓縮包會出現亂碼。 
  211.      * 使用 apache 中的這個類與 java 類庫中的用法是一新的,只是能設置編碼方式了。 
  212.      *   
  213.      * @param zos 
  214.      * @param bo 
  215.      * @param srcFile 
  216.      * @param prefixDir 
  217.      * @throws IOException 
  218.      * @throws FileNotFoundException 
  219.      */ 
  220.     private static void writeRecursive(ZipOutputStream zos, BufferedOutputStream bo, 
  221.             File srcFile, String prefixDir) throws IOException, FileNotFoundException { 
  222.         ZipEntry zipEntry; 
  223.  
  224.         String filePath = srcFile.getAbsolutePath().replaceAll("\\\\", "/").replaceAll( 
  225.                 "//""/"); 
  226.         if (srcFile.isDirectory()) { 
  227.             filePath = filePath.replaceAll("/$""") + "/"
  228.         } 
  229.         String entryName = filePath.replace(prefixDir, "").replaceAll("/$"""); 
  230.         if (srcFile.isDirectory()) { 
  231.             if (!"".equals(entryName)) { 
  232.                 System.out.println("正在創建目錄 - " + srcFile.getAbsolutePath() 
  233.                         + "  entryName=" + entryName); 
  234.  
  235.                 //如果是目錄,則需要在寫目錄後面加上 /  
  236.                 zipEntry = new ZipEntry(entryName + "/"); 
  237.                 zos.putNextEntry(zipEntry); 
  238.             } 
  239.  
  240.             File srcFiles[] = srcFile.listFiles(); 
  241.             for (int i = 0; i < srcFiles.length; i++) { 
  242.                 writeRecursive(zos, bo, srcFiles[i], prefixDir); 
  243.             } 
  244.         } else { 
  245.             System.out.println("正在寫文件 - " + srcFile.getAbsolutePath() + "  entryName=" 
  246.                     + entryName); 
  247.             BufferedInputStream bi = new BufferedInputStream(new FileInputStream(srcFile)); 
  248.  
  249.             //開始寫入新的ZIP文件條目並將流定位到條目數據的開始處 
  250.             zipEntry = new ZipEntry(entryName); 
  251.             zos.putNextEntry(zipEntry); 
  252.             byte[] buffer = new byte[1024]; 
  253.             int readCount = bi.read(buffer); 
  254.  
  255.             while (readCount != -1) { 
  256.                 bo.write(buffer, 0, readCount); 
  257.                 readCount = bi.read(buffer); 
  258.             } 
  259.             //注,在使用緩衝流寫壓縮文件時,一個條件完後一定要刷新一把,不 
  260.             //然可能有的內容就會存入到後麪條目中去了 
  261.             bo.flush(); 
  262.             //文件讀完後關閉 
  263.             bi.close(); 
  264.         } 
  265.     } 

要想把文件加入壓縮包,你必須將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不光能用來壓縮文件——它還能壓縮任何東西,包括要通過網絡傳輸的數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章