參考博客:java
ZIP4J,做爲解決了個人問題的終極解決方案,原本一開始在搜索引擎上就看到了它的蹤影,但因天朝的網絡環境問題,zip4j的官網一直沒法訪問,最終使我多走了好多冤枉路,期間試過JDK的zip包,試過Apache的zip解決方案,也試過如winzipaes等其它的開源框架,最終沒有知足本身的需求,最後,我不得已掛了一下代理將zip4j下載了下來,試用了一下,果真威力無比,所到之處所向披靡...算法
閒話少說,若是須要能夠到zip4j的官網下載該開源項目:數組
http://www.lingala.net/zip4j/網絡
不過須要提醒的是可能沒法直接訪問,若是沒法正常訪問,請自行準備代理訪問,若是各位嫌麻煩,能夠到這裏下載:框架
http://pan.baidu.com/s/1kUBFSwv ximuide
官網上下載的資源好像是不帶API幫助文檔的,我利用其源碼生成了一份,也一併打在個人資源文件中,但願能幫到你們。工具
(本身翻譯了一下,英文很差,呵呵...)post
Key features(主要特性):學習
從上面的主要特性能夠看出,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 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 (!passwd.isEmpty()) { 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 (destParam.isEmpty()) { 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); }
我本身的使用實例:
public class MainActivity extends AppCompatActivity { private TextView textView; private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); try { Unzip(new File("/mnt/usb_storage/USB_DISK1/abc.zip"), "/mnt/usb_storage/USB_DISK1/", "fe9d814e380c829b20ba0f561f70fa1d", "gbk", false); } catch (ZipException e) { e.printStackTrace(); Log.e(TAG, "解壓錯誤:" + e.toString()); } } Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case CompressStatus.START: { textView.setText("Start..."); break; } case CompressStatus.HANDLING: { Bundle bundle = msg.getData(); int percent = bundle.getInt(CompressKeys.PERCENT); setTitle(percent + "%"); textView.setText(percent + "%"); break; } case CompressStatus.ERROR: { Bundle bundle = msg.getData(); String error = bundle.getString(CompressKeys.ERROR); setTitle(error); break; } case CompressStatus.COMPLETED: { textView.setText("Completed"); break; } } } }; public void Unzip(final File zipFile, String dest, String passwd, String charset, final boolean isDeleteZipFile) throws ZipException { ZipFile zFile = new ZipFile(zipFile); if (TextUtils.isEmpty(charset)) { charset = "gbk"; } zFile.setFileNameCharset(charset); if (!zFile.isValidZipFile()) { throw new ZipException("Compressed files are not illegal, may be damaged."); } File destDir = new File(dest); if (destDir.isDirectory() && !destDir.exists()) { destDir.mkdir(); } if (zFile.isEncrypted()) { zFile.setPassword(passwd.toCharArray()); } final ProgressMonitor progressMonitor = zFile.getProgressMonitor(); Thread progressThread = new Thread(new Runnable() { @Override public void run() { Bundle bundle = null; Message msg = null; try { int percentDone = 0; if (mHandler == null) { return; } mHandler.sendEmptyMessage(CompressStatus.START); while (true) { Thread.sleep(1000); percentDone = progressMonitor.getPercentDone(); bundle = new Bundle(); bundle.putInt(CompressKeys.PERCENT, percentDone); msg = new Message(); msg.what = CompressStatus.HANDLING; msg.setData(bundle); mHandler.sendMessage(msg); if (percentDone >= 100) { break; } } mHandler.sendEmptyMessage(CompressStatus.COMPLETED); } catch (InterruptedException e) { bundle = new Bundle(); bundle.putString(CompressKeys.ERROR, e.getMessage()); msg = new Message(); msg.what = CompressStatus.ERROR; msg.setData(bundle); mHandler.sendMessage(msg); e.printStackTrace(); } finally { if (isDeleteZipFile) { zipFile.deleteOnExit(); } } } }); progressThread.start(); zFile.setRunInThread(true); zFile.extractAll(dest); } }
public class CompressStatus { public final static int START = 0; public final static int HANDLING = 1; public final static int COMPLETED = 2; public final static int ERROR = 3; }
public class CompressKeys { public final static String PERCENT="PERCENT"; public final static String ERROR="ERROR"; }