zip4j — Java處理zip壓縮檔的完整解決方案

來源:互聯網
上載者:User

前言

一個多月前,因項目需要對Java語言下的zip格式壓縮檔的處理作了一些瞭解,嘗試了多種開源項目並寫了幾篇部落格做記錄:

  • http://blog.csdn.net/zhangyihui1986/article/details/7724229
  • http://blog.csdn.net/zhangyihui1986/article/details/7723649
  • http://blog.csdn.net/zhangyihui1986/article/details/7724616

 ZIP4J,作為解決了我的問題的終極解決方案,本來一開始在搜尋引擎上就看到了它的蹤跡,但因天朝的網路環境問題,zip4j的官網一直無法訪問,最終使我多走了好多冤枉路,期間試過JDK的zip包,試過Apache的zip解決方案,也試過如winzipaes等其它的開源架構,最終沒有滿足自己的需求,最後,我不得已掛了一下代理將zip4j下載了下來,試用了一下,果然威力無比,所到之處所向披靡...

閑話少說,如果需要可以到zip4j的官網下載該開源項目:

http://www.lingala.net/zip4j/

不過需要提醒的是可能無法直接存取,如果無法正常訪問,請自行準備代理訪問,如果各位嫌麻煩,可以到這裡下載:

http://download.csdn.net/detail/zhangyihui1986/4418509

這是我的CSDN資源連結,下載需要3分,您如果分數不多,可以留言索取,呵呵...我也需要積分,請諒解!

官網上下載的資源好像是不帶API協助文檔的,我利用其源碼產生了一份,也一併打在我的資源檔中,希望能幫到大家。

ZIP4J的官方說明

(自己翻譯了一下,英文不好,呵呵...)

Key features(主要特性):

  • Create, Add, Extract, Update, Remove files from a Zip file
    針對ZIP壓縮檔建立、添加、抽出、更新和移除檔案
  • Read/Write password protected Zip files
    (讀寫有密碼保護的Zip檔案)
  • Supports AES 128/256 Encryption
    (支援AES 128/256演算法加密)
  • Supports Standard Zip Encryption
    (支援標準Zip演算法加密)
  • Supports Zip64 format
    (支援zip64格式)
  • Supports Store (No Compression) and Deflate compression method
    (支援Store(非壓縮)和Deflate壓縮方法---不太明白)
  • Create or extract files from Split Zip files (Ex: z01, z02,...zip)
    (針對分塊zip檔案建立和抽出檔案)
  • Supports Unicode file names
    (支援Unicode編碼檔案名稱)
  • Progress Monitor
    (進度監控)

從上面的主要特性可以看出,zip4j的功能是非常強大的,完全可以利用其寫個類似於好壓的zip檔案管理軟體,但我們用地最多的可能還是利用其作一些簡單的解壓和壓縮操作,其它的功能極少觸碰,我也一樣,呵呵...

使用注意點

zip4j預設採用UTF-8編碼,所以它支援中文,同時也支援密碼,而且支援多種壓縮演算法,可以說功能強大,但使用起來卻非常簡單,當然,如果需求比較複雜,那就得好好去研究了。如果你僅僅是簡單地解壓一個zip壓縮檔,那麼只需要簡單地幾步即可:

    public static void unzip(File zipFile, String dest, String passwd) throws ZipException {              ZipFile zFile = new ZipFile(zipFile);  // 首先建立ZipFile指向磁碟上的.zip檔案              zFile.setFileNameCharset("GBK");       // 設定檔案名稱編碼,在GBK系統中需要設定              if (!zFile.isValidZipFile()) {   // 驗證.zip檔案是否合法,包括檔案是否存在、是否為zip檔案、是否被損壞等                  throw new ZipException("壓縮檔不合法,可能被損壞.");              }              File destDir = new File(dest);     // 解壓目錄              if (destDir.isDirectory() && !destDir.exists()) {                  destDir.mkdir();              }              if (zFile.isEncrypted()) {                  zFile.setPassword(passwd.toCharArray());  // 設定密碼              }              zFile.extractAll(dest);      // 將檔案抽出到解壓目錄(解壓)          }  

 當然將指定檔案壓縮成zip檔案也是非常簡單的事,此處不再貼代碼,如有需要請參看下面的完整樣本。

 提示:如果將要解壓的壓縮檔中的檔案名稱含有中文,解壓時需要注意一點,就是設定檔案名稱字元集方法

zFile.setFileNameCharset("GBK"); 

這個方法的調用一定要靠前,要靠多前呢?其實最好在建立ZipFile之後就設定上,至少要在

zFile.isValidZipFile()  

這個方法調用之前調用,我在應用時因為這個問題耽誤好久,最後查看源碼才弄明白,好像是ZipFile在驗證方法中就將編碼設定好,以後就不再對檔案名稱編碼作改變了。

完整樣本

下面提供一個自己寫的例子,鄙人才疏學淺,天分也差,寫的代碼品質很差,鬥膽貼上,希望能起到拋磚引玉的作用。

package com.ninemax.cul.util;import java.io.File;import java.util.ArrayList;import java.util.Collections;import java.util.List;import org.apache.commons.lang3.StringUtils;import net.lingala.zip4j.core.ZipFile;import net.lingala.zip4j.exception.ZipException;import net.lingala.zip4j.model.FileHeader;import net.lingala.zip4j.model.ZipParameters;import net.lingala.zip4j.util.Zip4jConstants;/** * ZIP壓縮檔操作工具類 * 支援密碼 * 依賴zip4j開源項目(http://www.lingala.net/zip4j/) * 版本1.3.1 * @author ninemax */public class CompressUtil {/** * 使用給定密碼解壓指定的ZIP壓縮檔到指定目錄 * <p> * 如果指定目錄不存在,可以自動建立,不合法的路徑將導致異常被拋出 * @param zip 指定的ZIP壓縮檔 * @param dest 解壓目錄 * @param passwd ZIP檔案的密碼 * @return 解壓後檔案數組 * @throws ZipException 壓縮檔有損壞或者解壓縮失敗拋出 */public static File [] unzip(String zip, String dest, String passwd) throws ZipException {File zipFile = new File(zip);return unzip(zipFile, dest, passwd);}/** * 使用給定密碼解壓指定的ZIP壓縮檔到目前的目錄 * @param zip 指定的ZIP壓縮檔 * @param passwd ZIP檔案的密碼 * @return  解壓後檔案數組 * @throws ZipException 壓縮檔有損壞或者解壓縮失敗拋出 */public static File [] unzip(String zip, String passwd) throws ZipException {File zipFile = new File(zip);File parentDir = zipFile.getParentFile();return unzip(zipFile, parentDir.getAbsolutePath(), passwd);}/** * 使用給定密碼解壓指定的ZIP壓縮檔到指定目錄 * <p> * 如果指定目錄不存在,可以自動建立,不合法的路徑將導致異常被拋出 * @param zip 指定的ZIP壓縮檔 * @param dest 解壓目錄 * @param passwd ZIP檔案的密碼 * @return  解壓後檔案數組 * @throws ZipException 壓縮檔有損壞或者解壓縮失敗拋出 */public static File [] unzip(File zipFile, String dest, String passwd) throws ZipException {ZipFile zFile = new ZipFile(zipFile);zFile.setFileNameCharset("GBK");if (!zFile.isValidZipFile()) {throw new ZipException("壓縮檔不合法,可能被損壞.");}File destDir = new File(dest);if (destDir.isDirectory() && !destDir.exists()) {destDir.mkdir();}if (zFile.isEncrypted()) {zFile.setPassword(passwd.toCharArray());}zFile.extractAll(dest);List<FileHeader> headerList = zFile.getFileHeaders();List<File> extractedFileList = new ArrayList<File>();for(FileHeader fileHeader : headerList) {if (!fileHeader.isDirectory()) {extractedFileList.add(new File(destDir,fileHeader.getFileName()));}}File [] extractedFiles = new File[extractedFileList.size()];extractedFileList.toArray(extractedFiles);return extractedFiles;}/** * 壓縮指定檔案到當前檔案夾 * @param src 要壓縮的指定檔案 * @return 最終的壓縮檔存放的絕對路徑,如果為null則說明壓縮失敗. */public static String zip(String src) {return zip(src,null);}/** * 使用給定密碼壓縮指定檔案或檔案夾到目前的目錄 * @param src 要壓縮的檔案 * @param passwd 壓縮使用的密碼 * @return 最終的壓縮檔存放的絕對路徑,如果為null則說明壓縮失敗. */public static String zip(String src, String passwd) {return zip(src, null, passwd);}/** * 使用給定密碼壓縮指定檔案或檔案夾到目前的目錄 * @param src 要壓縮的檔案 * @param dest 壓縮檔存放路徑 * @param passwd 壓縮使用的密碼 * @return 最終的壓縮檔存放的絕對路徑,如果為null則說明壓縮失敗. */public static String zip(String src, String dest, String passwd) {return zip(src, dest, true, passwd);}/** * 使用給定密碼壓縮指定檔案或檔案夾到指定位置. * <p> * dest可傳最終壓縮檔存放的絕對路徑,也可以傳存放目錄,也可以傳null或者"".<br /> * 如果傳null或者""則將壓縮檔存放在目前的目錄,即跟源檔案同目錄,壓縮檔名取源檔案名稱,以.zip為尾碼;<br /> * 如果以路徑分隔字元(File.separator)結尾,則視為目錄,壓縮檔名取源檔案名稱,以.zip為尾碼,否則視為檔案名稱. * @param src 要壓縮的檔案或檔案夾路徑 * @param dest 壓縮檔存放路徑 * @param isCreateDir 是否在壓縮檔裡建立目錄,僅在壓縮檔為目錄時有效.<br /> * 如果為false,將直接壓縮目錄下檔案到壓縮檔. * @param passwd 壓縮使用的密碼 * @return 最終的壓縮檔存放的絕對路徑,如果為null則說明壓縮失敗. */public static String zip(String src, String dest, boolean isCreateDir, String passwd) {File srcFile = new File(src);dest = buildDestinationZipFilePath(srcFile, dest);ZipParameters parameters = new ZipParameters();parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);// 壓縮方式parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);// 壓縮層級if (!StringUtils.isEmpty(passwd)) {parameters.setEncryptFiles(true);parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD);// 加密方式parameters.setPassword(passwd.toCharArray());}try {ZipFile zipFile = new ZipFile(dest);if (srcFile.isDirectory()) {// 如果不建立目錄的話,將直接把給定目錄下的檔案壓縮到壓縮檔,即沒有目錄結構if (!isCreateDir) {File [] subFiles = srcFile.listFiles();ArrayList<File> temp = new ArrayList<File>();Collections.addAll(temp, subFiles);zipFile.addFiles(temp, parameters);return dest;}zipFile.addFolder(srcFile, parameters);} else {zipFile.addFile(srcFile, parameters);}return dest;} catch (ZipException e) {e.printStackTrace();}return null;}/** * 構建壓縮檔存放路徑,如果不存在將會建立 * 傳入的可能是檔案名稱或者目錄,也可能不傳,此方法用以轉換最終壓縮檔的存放路徑 * @param srcFile 源檔案 * @param destParam 壓縮目標路徑 * @return 正確的壓縮檔存放路徑 */private static String buildDestinationZipFilePath(File srcFile,String destParam) {if (StringUtils.isEmpty(destParam)) {if (srcFile.isDirectory()) {destParam = srcFile.getParent() + File.separator + srcFile.getName() + ".zip";} else {String fileName = srcFile.getName().substring(0, srcFile.getName().lastIndexOf("."));destParam = srcFile.getParent() + File.separator + fileName + ".zip";}} else {createDestDirectoryIfNecessary(destParam);// 在指定路徑不存在的情況下將其建立出來if (destParam.endsWith(File.separator)) {String fileName = "";if (srcFile.isDirectory()) {fileName = srcFile.getName();} else {fileName = srcFile.getName().substring(0, srcFile.getName().lastIndexOf("."));}destParam += fileName + ".zip";}}return destParam;}/** * 在必要的情況下建立壓縮檔存放目錄,比如指定的存放路徑並沒有被建立 * @param destParam 指定的存放路徑,有可能該路徑並沒有被建立 */private static void createDestDirectoryIfNecessary(String destParam) {File destDir = null;if (destParam.endsWith(File.separator)) {destDir = new File(destParam);} else {destDir = new File(destParam.substring(0, destParam.lastIndexOf(File.separator)));}if (!destDir.exists()) {destDir.mkdirs();}}public static void main(String[] args) {zip("d:\\test\\cc", "d:\\test\\cc.zip", "11");//try {//File[] files = unzip("d:\\test\\漢字.zip", "aa");//for (int i = 0; i < files.length; i++) {//System.out.println(files[i]);//}//} catch (ZipException e) {//e.printStackTrace();//}}}

需要學習的東西太多,沒太多時間(或許只是借口)去研究它,上面的例子僅是簡單地解壓和壓縮操作;但在使用中可以發現Zip4J功能比較完備,如果需要更多地支援,那就真要好好去研究一下它,也許它真的不會使您失望。。。

補充

刪除壓縮檔中的目錄

看到有朋友在問如何刪除壓縮檔中的目錄,在這裡補充一下。

利用zip4j刪除壓縮檔中的目錄,查閱API後很容易想到這樣的方式:

ZipFile zipFile = new ZipFile("d:\\FeiQ-V2.5.zip");zipFile.setFileNameCharset("GBK");zipFile.removeFile("sounds/");// sounds是zip檔案中的一個目錄

但這種直接刪除壓縮檔中非空目錄的方式是不會成功的,你會看到zip檔案絲毫沒有變化,雖然目錄對應的FileHeader已被刪除(表現就是如果這時再將目錄下的所有檔案刪除,則該目錄隨之消失) ;因此我們需要將該目錄下所有的檔案都刪除掉,最後再將目錄刪除,根據這個思路,我們很容易形成如下的代碼:

void removeDirFromZipArchive(String file, String removeDir) throws ZipException {// 建立ZipFile並設定編碼ZipFile zipFile = new ZipFile(file);zipFile.setFileNameCharset("GBK");// 給要刪除的目錄加上路徑分隔字元if (!removeDir.endsWith(File.separator)) removeDir += File.separator;// 如果目錄不存在, 直接返回FileHeader dirHeader = zipFile.getFileHeader(removeDir);if (null == dirHeader) return;// 遍曆壓縮檔中所有的FileHeader, 將指定刪除目錄下的子檔案刪除List allHeaders = zipFile.getFileHeaders();for(int i=0, len = allHeaders.size(); i<len; i++) {FileHeader subHeader = (FileHeader) allHeaders.get(i);if (subHeader.getFileName().startsWith(dirHeader.getFileName())&& !subHeader.getFileName().equals(dirHeader.getFileName())) {zipFile.removeFile(subHeader);}}// 最後刪除指定目錄zipFile.removeFile(dirHeader);}

這樣仍然解決不了問題,如果你這樣做了,那麼你將會得到一個java.lang.IndexOutOfBoundsException異常,那麼看似正常的代碼為什麼會報索引越界異常呢?其實我們通過zipFile.getFileHeaders()方法得到的List會隨遍曆中的刪除操作而發生變化,也就是說我們刪除了某個FileHeader,將會反映到該List中。每成功刪除一個FileHeader,List長度就減1,而i一直在0至List的初始長度之間遞增,反覆幾次後就可能出現越界異常。

為了避免這種情況發生,我們可以多做一些操作,比如可以在遍曆中暫不進行刪除操作,而只是將要刪除的檔案記錄下來,遍曆結束後再統一刪除,最後將目錄刪除,經測試,這個思路可以解決問題。

簡單範例程式碼:

void removeDirFromZipArchive(String file, String removeDir) throws ZipException {// 建立ZipFile並設定編碼ZipFile zipFile = new ZipFile(file);zipFile.setFileNameCharset("GBK");// 給要刪除的目錄加上路徑分隔字元if (!removeDir.endsWith(File.separator)) removeDir += File.separator;// 如果目錄不存在, 直接返回FileHeader dirHeader = zipFile.getFileHeader(removeDir);if (null == dirHeader) return;// 遍曆壓縮檔中所有的FileHeader, 將指定刪除目錄下的子檔案名稱儲存起來List headersList = zipFile.getFileHeaders();List<String> removeHeaderNames = new ArrayList<String>();for(int i=0, len = headersList.size(); i<len; i++) {FileHeader subHeader = (FileHeader) headersList.get(i);if (subHeader.getFileName().startsWith(dirHeader.getFileName())&& !subHeader.getFileName().equals(dirHeader.getFileName())) {removeHeaderNames.add(subHeader.getFileName());}}// 遍曆刪除指定目錄下的所有子檔案, 最後刪除指定目錄(此時已為空白目錄)for(String headerNameString : removeHeaderNames) {zipFile.removeFile(headerNameString);}zipFile.removeFile(dirHeader);}

也許還有其它的辦法來解決此問題,如果您有需要,就留待您來解決了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.