這邊紀錄下使用 Java 壓縮/解壓縮 Zip 的方法,
以下先直接上程式碼:
ZipTest.java:
package main;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.Queue;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class ZipTest {
public static void main(String[] args) throws IOException {
String srcFilePath_isFile = "D:\\某檔案.jpg";
String srcFilePath_isDirectory = "D:\\某資料夾";
String toZipPath = "D:\\壓縮檔.zip";
String toUnzipDirPath = "D:\\壓縮檔解開後要輸出到的資料夾";
//----- zip file -----
zipFile_onlyForSingleFile(srcFilePath_isFile, toZipPath); //壓縮單一檔案
zipFile_onlyForSingleFile(srcFilePath_isDirectory, toZipPath); //壓縮單一資料夾,不包括資料夾內的檔案
zipFile_canAlsoHandleDirectory_stackVersion(srcFilePath_isDirectory, toZipPath); //壓縮檔案或資料夾,使用佇列實現
zipFile_canAlsoHandleDirectory_recursionVersion(srcFilePath_isDirectory, toZipPath); //壓縮檔案或資料夾,使用遞迴實現
//----- unzip file -----
unzipFile_byZipFile(toZipPath, toUnzipDirPath); //解壓縮,使用 ZipFile
unzipFile_byZipInputStream(toZipPath, toUnzipDirPath); //解壓縮,使用 ZipInputStream
System.out.println("Done");
}
/******************** Zip file *****************/
public static void zipFile_onlyForSingleFile(String srcPath, String toPath) throws IOException {
File srcFile = new File(srcPath);
File zipFile = new File(toPath);
FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
ZipEntry zipEntry = new ZipEntry(srcFile.getName() + (srcFile.isDirectory() ? File.separator : ""));
zipOutputStream.putNextEntry(zipEntry);
if (srcFile.isFile()) {
// only srcFile is a file (not a directory) needs to write binary content of
// file
FileInputStream fileInputStream = new FileInputStream(srcFile);
zipOutputStream.write(fileInputStream.readAllBytes());
fileInputStream.close();
}
zipOutputStream.close();
fileOutputStream.close();
}
public static void zipFile_canAlsoHandleDirectory_stackVersion(String srcPath, String toPath) throws IOException {
File srcFile = new File(srcPath);
String baseFileName = srcFile.getName();
Path baseFilePath = Paths.get(srcPath);
File zipFile = new File(toPath);
FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
// use Queue to implement a BFS(Breadth-First Search) way to read all files and
// directory
Queue<File> fileQueue = new LinkedList<File>();
fileQueue.add(srcFile);
while (fileQueue.size() > 0) {
File firstFileInQueue = fileQueue.poll();
String relativePath = baseFileName + File.separator + baseFilePath.relativize(firstFileInQueue.toPath());
if (firstFileInQueue.isFile()) {
// do zip for file
FileInputStream fileInputStream = new FileInputStream(firstFileInQueue);
ZipEntry zipEntry = new ZipEntry(relativePath);
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.write(fileInputStream.readAllBytes());
fileInputStream.close();
} else if (firstFileInQueue.isDirectory()) {
File[] childFileList = firstFileInQueue.listFiles();
if (childFileList != null && childFileList.length > 0) {
// add files inside directory into queue
fileQueue.addAll(Arrays.asList(firstFileInQueue.listFiles()));
} else {
// if it is an empty directory,
// just put a zipEntry and don't need to write binary content (And of course you
// can't get binary content from a directory.)
// don't need to do specific thing to non-empty directory because directory will
// appear in zip when you zip files inside the directory
ZipEntry zipEntry = new ZipEntry(relativePath + File.separator); // you should add a File.separator
// to let zip know it is a
// directory
zipOutputStream.putNextEntry(zipEntry);
}
}
}
zipOutputStream.close();
fileOutputStream.close();
}
public static void zipFile_canAlsoHandleDirectory_recursionVersion(String srcPath, String toPath)
throws IOException {
File zipFile = new File(toPath);
FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
zipFile_canAlsoHandleDirectory_recursionVersion_helper(srcPath, srcPath, toPath, zipOutputStream);
zipOutputStream.close();
fileOutputStream.close();
}
private static void zipFile_canAlsoHandleDirectory_recursionVersion_helper(String basePath, String srcPath,
String toPath, ZipOutputStream zipOutputStream) throws IOException {
String baseFileName = new File(basePath).getName();
Path baseFilePath = Paths.get(basePath);
File srcFile = new File(srcPath);
File zipFile = new File(toPath);
if (srcFile.isFile()) {
// do zip for file
String relativePath = baseFileName + File.separator + baseFilePath.relativize(srcFile.toPath());
FileInputStream fileInputStream = new FileInputStream(srcFile);
ZipEntry zipEntry = new ZipEntry(relativePath);
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.write(fileInputStream.readAllBytes());
fileInputStream.close();
} else if (srcFile.isDirectory()) {
File[] childFileList = srcFile.listFiles();
if (childFileList != null && childFileList.length > 0) {
for (File childFile : childFileList) {
zipFile_canAlsoHandleDirectory_recursionVersion_helper(basePath, childFile.getPath(), toPath,
zipOutputStream);
}
} else {
String relativePath = baseFileName + File.separator + baseFilePath.relativize(srcFile.toPath());
ZipEntry zipEntry = new ZipEntry(relativePath + File.separator);
zipOutputStream.putNextEntry(zipEntry);
}
}
}
/******************** Unzip file *****************/
public static void unzipFile_byZipInputStream(String zipFilePath, String toPath) throws IOException {
File toPathFile = new File(toPath);
if (!toPathFile.exists()) {
toPathFile.mkdirs();
}
FileInputStream fileInputStream = new FileInputStream(zipFilePath);
ZipInputStream zipInputStream = new ZipInputStream(fileInputStream);
ZipEntry zipEntry = zipInputStream.getNextEntry();
while(zipEntry != null) {
File file = new File(toPath + File.separator + zipEntry.getName());
//check is zip Entry a file or an directory
//don't use zipEntry.isDirectory() becuase it only use "zipEntry.getName().endsWith("/")" to check
if (zipEntry.getName().endsWith(File.separator) || zipEntry.getName().endsWith("/")) {
if (!file.exists()) {
file.mkdirs();
}
}else {
if (!file.exists()) {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(zipInputStream.readAllBytes());
fileOutputStream.close();
}
}
zipEntry = zipInputStream.getNextEntry();
}
zipInputStream.close();
fileInputStream.close();
}
public static void unzipFile_byZipFile(String zipFilePath, String toPath) throws IOException {
File toPathFile = new File(toPath);
if (!toPathFile.exists()) {
toPathFile.mkdirs();
}
ZipFile zipFile = new ZipFile(zipFilePath);
Enumeration<? extends ZipEntry> zipEntryEnumeration = zipFile.entries();
while(zipEntryEnumeration.hasMoreElements()) {
ZipEntry zipEntry = zipEntryEnumeration.nextElement();
File file = new File(toPath + File.separator + zipEntry.getName());
//check is zip Entry a file or an directory
//don't use zipEntry.isDirectory() becuase it only use "zipEntry.getName().endsWith("/")" to check
if (zipEntry.getName().endsWith(File.separator) || zipEntry.getName().endsWith("/")) {
if (!file.exists()) {
file.mkdirs();
}
}else {
if (!file.exists()) {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
InputStream zipFileInputStream = zipFile.getInputStream(zipEntry);
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(zipFileInputStream.readAllBytes());
fileOutputStream.close();
zipFileInputStream.close();
}
}
}
zipFile.close();
}
}
說明:
上述程式碼展示了壓縮及解壓縮的各種不同方法,
zipFile_onlyForSingleFile() 只是展示了基本用法,只處理單一檔案或單一資料夾,
可以注意到幾點:
- 當處理資料夾時,只需要放入代表檔案 (或資料夾) 的 ZipEntry
zipOutputStream.putEntry(zipEntry);
不需要再寫入檔案的二進位資料,
zipOutputStream.write(fileInputStream.readAllBytes());
而如果是處理檔案時就需要再寫入檔案的二進位資料。 - 設定 new ZipEntry(String name) 時,需要 name 的參數,
其代表檔案或資料夾的路徑(連同名字),路徑是相對於壓縮檔 root 位置,
例如:
xxx/yyy/zzz/someFile.jpg
xxx/yyy/zzz/someDirectory/
要注意如果是資料夾的話,要在最後面加上檔案路徑的分隔符號,例如 "/"
zipFile_canAlsoHandleDirectory_stackVersion() 和
zipFile_canAlsoHandleDirectory_recursionVersion() 展示了
如何壓縮一個內含多檔案(或資料夾)的巢狀結構 (即可能有多層資料夾 ) 資料夾的方法,
原理跟 zipFile_onlyForSingleFile() 一樣,只是對資料夾內的各層資料夾及內部檔案一個個的
去做設定 ZipEntry 的動作,
zipOutputStream.putEntry(zipEntry);
zipOutputStream.write(fileInputStream.readAllBytes());
只是遍歷檔案的實現方式不同而已,
zipFile_canAlsoHandleDirectory_stackVersion() 使用了佇列 (stack) 來實現,
zipFile_canAlsoHandleDirectory_recursionVersion() 使用了遞迴 (resurisive) 來實現。
在解壓縮的部份,展示了兩個方法:
unzipFile_byZipFile() 和
unzipFile_byZipInputStream(),
基本差異不大,只是使用的幫助 Class 不同而已,
unzipFile_byZipFile() 用了 ZipFile,而
unzipFile_byZipInputStream() 用了 ZipInputStream,
需要注意的是,
ZipEntry.isDirectory() 方法不是一個正確獲取 ZipEntry 是否為資料夾的好方法,
我們可以從源碼中可以看到如下程式碼:
public class ZipEntry implements ZipConstants, Cloneable {
..............
public boolean isDirectory() {
return name.endsWith("/");
}
..............
}可以發現 isDirectory() 只是單純判斷了 ZipEntry 的 name 後面是否是 "/" 結尾,
但是如果如上述程式,我們在壓縮檔案時用 File.separator 來設定 ZipEntry 的檔案路徑分隔符的話,
判斷 ZipEntry 是否為資料夾就不應只是判斷結尾是否是 "/" ,而是看所在系統而有所不同 (例如 Unix 系統或 Windows 系統),例如有可能分隔符會是 "/" 或 "\" 。
沒有留言 :
張貼留言