zip壓縮檔案處理方案(Zip4j壓縮和解壓)

OkidoGreen發表於2020-04-05

https://blog.csdn.net/ditto_zhou/article/details/82977395

主要特性

  • 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預設採用utf-8編碼,支援中文,也支援密碼和多種壓縮演算法,可以說是功能強大,只是簡單的解壓檔案,按照下面幾步

public static void upzip(File zipFile,String dest,String password) {
		try {
			ZipFile zFile = new ZipFile(zipFile);//指向壓縮檔案.zip
			//設定檔名編碼,在gbk系統中需要設定,檔名稱是中文就不會亂碼
                        zFile.setFileNameCharset("gbk");
			if(!zFile.isValidZipFile()) {// 檢查檔案是否合法
				throw new Exception("檔名不合法");
			}
			File destFile = new File(dest);//解壓目錄
			if(destFile.isDirectory()&&!destFile.exists()) {
				destFile.mkdirs();
			}
			if(zFile.isEncrypted()) {//如果設定了密碼
				zFile.setPassword(password.toCharArray());
			}
			zFile.extractAll(dest);//將檔案輸出到目標目錄
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

注意,如果解壓的檔案中檔名包含中文,一定要設定檔名字符集編碼

zFile.setFileNameCharset("gbk");

對檔名編碼字符集方法的呼叫一定要靠前,必須在isValidZipFile方法前呼叫,因為isValidZipFile方法的呼叫,會去設定檔名的編碼

zFile.isValidZipFile()
zipModel = headerReader.readAllHeaders(this.fileNameCharset);

壓縮和解壓

public class CompressUtil {
	private static Logger logger = Logger.getLogger(CompressUtil.class);
	/**
	 * 解壓檔案
	 * 
	 * @param zipFile  壓縮檔案
	 * @param dest     解壓檔案的存放目錄
	 * @param password 壓縮檔案的密碼
	 */
	public static void unzip(File zipFile, String dest, String password) {
		try {
			ZipFile zFile = new ZipFile(zipFile);// 指向壓縮檔案.zip
			zFile.setFileNameCharset("gbk");// 設定檔名編碼,在gbk系統中需要設定
			if (!zFile.isValidZipFile()) {// 檢查檔案是否合法
				throw new Exception("檔名不合法");
			}
			File destFile = new File(dest);// 解壓目錄
			if (destFile.isDirectory() && !destFile.exists()) {
				destFile.mkdirs();
			}
			if (zFile.isEncrypted()) {// 如果設定了密碼
				zFile.setPassword(password.toCharArray());
			}
			zFile.extractAll(dest);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 返回的是解壓後的檔案陣列
	 * 
	 * @param zipFile 壓縮檔案路徑
	 * @param desc    解壓後檔案的存放路徑
	 * @param passwd  密碼
	 * @param charset 檔名稱編碼
	 * @return
	 */
	public static File[] getFilesByUnzip(File zipFile, String dest, String passwd, String charset) {
		try {
			ZipFile zFile = new ZipFile(zipFile);
			zFile.setFileNameCharset(charset);
			if (!zFile.isValidZipFile()) {
				throw new Exception("檔名稱不合法");
			}
			File destFile = new File(dest);
			if (destFile.isDirectory() && !destFile.exists()) {
				destFile.mkdirs();
			}
			if (zFile.isEncrypted()) {
				zFile.setPassword(passwd);
			}
			zFile.extractAll(dest);
			// 獲取檔案列表
			List fileHeaders = zFile.getFileHeaders();
			List<File> extractFiles = new ArrayList<>();
			if (fileHeaders != null && fileHeaders.size() > 0) {
				for (int i = 0; i < fileHeaders.size(); i++) {
					FileHeader header = (FileHeader) fileHeaders.get(i);
					if (header != null) {
						extractFiles.add(new File(destFile, header.getFileName()));
					}
				}
			}
			File[] arr = new File[extractFiles.size()];
			return extractFiles.toArray(arr);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 使用給定密碼壓縮指定檔案或資料夾到指定位置. dest可傳最終壓縮檔案存放的絕對路徑,也可以傳存放目錄,也可以傳null或者"".
	 * 如果傳null或者""則將壓縮檔案存放在當前目錄,即跟原始檔同目錄,壓縮檔名取原始檔名,以.zip為字尾;
	 * 如果以路徑分隔符(File.separator)結尾,則視為目錄,壓縮檔名取原始檔名,以.zip為字尾,否則視為檔名.
	 * @param src         要壓縮的檔案或資料夾路徑
	 * @param dest        壓縮檔案存放路徑
	 * @param isCreateDir 是否在壓縮檔案裡建立目錄,僅在壓縮檔案為目錄時有效. 如果為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 zipParameters = new ZipParameters();
		zipParameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);//設定壓縮方法是deflate
		zipParameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);//設定壓縮級別
		if(passwd!=null&&passwd.trim().length()>0) {
			// 如果密碼不為空,壓縮包進行加密,需要設定壓縮演算法
			zipParameters.setEncryptFiles(true);
			zipParameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD);//加密演算法設定為standard
			zipParameters.setPassword(passwd.toCharArray());
		}
		try {
			ZipFile zipFile = new ZipFile(dest);
			if(srcFile.isDirectory()) {
				if(!isCreateDir) {//如果false,表示不按照目錄結構進行壓縮
					ArrayList<File> list = new ArrayList<>();
					Collections.addAll(list, srcFile.listFiles());
					zipFile.addFiles(list, zipParameters);
				}else {
					//按照目錄結構壓縮
					zipFile.addFolder(srcFile, zipParameters);
				}
			}else {
				zipFile.addFile(srcFile, zipParameters);
			}
		} catch (ZipException e) {
			logger.error("-----檔案壓縮失敗-----");
			logger.error(e.getMessage());
			dest = null;
		}
		return dest;

	}
	
	/**
	 * 構建壓縮檔案存放路徑,如果不存在將會建立 * 傳入的可能是檔名或者目錄,也可能不傳,此方法用以轉換最終壓縮檔案的存放路徑 * 
	 * @param srcFile 原始檔 
	 * @param destParam 壓縮目標路徑 * 
	 * @return 正確的壓縮檔案存放路徑
	 */
	private static String buildDestinationZipFilePath(File srcFile, String destParam) {
		if (destParam == null || destParam.trim() == "") {
			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 file = null;
		if(destParam.endsWith(File.separator)) {
			file = new File(destParam);
		}else {
			file = new File(destParam.substring(0, destParam.lastIndexOf(File.separator)));
		}
		if(file.exists()) {
			file.mkdirs();
		}
	}
}

刪除

進行刪除操作前, 也要設定檔名編碼為gbk,因為如果是中文名稱的檔案,就會丟擲could not find file header for file的錯誤

刪除檔案

根據檔名稱或者FileHeader刪除

public RemoveFileFromZipFile() {
		
		try {
			// Initiate ZipFile object with the path/name of the zip file.
			ZipFile zipFile = new ZipFile("d:\\1.zip");
			zipFile.setFileNameCharset("gbk");
			// Note: If this zip file is a split file then this method throws an exception as
			// Zip Format Specification does not allow updating split zip files
			
			// Please make sure that this zip file has more than one file to completely test
			// this example
			
			// Removal of a file from a zip file can be done in two ways:
			// 1. 直接根據檔名稱刪除
			zipFile.removeFile("新建文字文件.txt");
			
			// 2. 建立FileHeader,根據FileHeader刪除
			if (zipFile.getFileHeaders() != null && zipFile.getFileHeaders().size() > 0) {
				zipFile.removeFile((FileHeader)zipFile.getFileHeaders().get(0));
			} else {
				System.out.println("This cannot be demonstrated as zip file does not have any files left");
			}
			
		} catch (ZipException e) {
			e.printStackTrace();
		}
		

刪除目錄

將目錄下面的檔案刪除,在刪除目錄

/**
	 * 刪除壓縮檔案中的檔案或者目錄
	 * @param file 壓縮檔案的路徑
	 * @param removeFile 檔名或者目錄
	 */
	public static void removeDirFromZip(String file,String removeFile) {
		try {
			ZipFile zipFile = new ZipFile(file);
			zipFile.setFileNameCharset("gbk");//設定檔名的編碼格式
			if(!removeFile.endsWith("/")) {
				//刪除檔案的方式,第一種是根據檔案的名稱,第二種是根據檔案的fileHeader進行刪除
				//zipFile.removeFile(removeFile);
				FileHeader header = new FileHeader();
				header.setFileName(removeFile);
				zipFile.removeFile(header);
			}else {
				//如果是目錄的話,需要先將目錄下面的檔案刪除掉,在刪除目錄
				if(!removeFile.endsWith("/")) removeFile +="/";
				List headers = zipFile.getFileHeaders();
				List<FileHeader> removeFiles = new ArrayList<>();
				for (int i = 0; i < headers.size(); i++) {
					FileHeader subHeader =(FileHeader) headers.get(i);
					if(subHeader.getFileName().contains(removeFile)
							&&!subHeader.getFileName().equals(removeFile)) {
						//不能直接刪除,會丟擲java.lang.IndexOutOfBoundsException
//						zipFile.removeFile(subHeader);
						// 這裡我們使用一個list記錄下刪除的subheader,在進行刪除
						removeFiles.add(subHeader);
					}
				}
				for (int i = 0; i < removeFiles.size(); i++) {
					zipFile.removeFile(removeFiles.get(i));
				}
				//最後將目錄刪除
				if(zipFile.getFileHeader(removeFile)!=null) {
					zipFile.removeFile(removeFile);
				}
			}
		} catch (ZipException e) {
			e.printStackTrace();
		}
	}

參考部落格:https://blog.csdn.net/zhyh1986/article/details/7921376

相關文章