遞迴將檔案/目錄壓縮為 ZIP 檔案,支援巢狀目錄結構。
`base` 代表「在 ZIP 檔案內部的相對路徑前綴」,用於構建每個條目 (ZipEntry) 的完整內部路徑。
private void zip(ZipOutputStream out, File f, String base) throws Exception {
if (f.isDirectory()) { // 1. 如果是目錄
File[] fl = f.listFiles(); // 取得子檔案/目錄陣列
out.putNextEntry(new ZipEntry(base + "/")); // 2. 建立目錄條目 (以 / 結尾)
base = base.length() == 0 ? "" : base + "/"; // 3. 更新 base:加上目錄名 + /
for (int i = 0; i < fl.length; i++) { // 4. 遞迴處理每個子項
zip(out, fl[i], base + fl[i].getName());
}
} else { // 5. 如果是檔案
out.putNextEntry(new ZipEntry(base)); // 建立檔案條目 (base 已含完整相對路徑)
FileInputStream in = new FileInputStream(f); // 開啟檔案輸入流
int b;
while ((b = in.read()) != -1) { // 逐位元組讀取
out.write(b); // 寫入 ZIP 輸出流
}
in.close(); // 關閉輸入流
}
}
邏輯:
目的:確保後續子項路徑正確拼接。
例子:目錄結構 `root/sub/file.txt`
| 遞迴層級 | f | base (呼叫前) | base (更新後) | 子項傳入 base |
|---|---|---|---|---|
| 1 | root/ | "" | "" | "" + "sub" = "sub" |
| 2 | sub/ | "sub" | "sub/" | "sub/" + "file.txt" = "sub/file.txt" |
| 3 | file.txt | "sub/file.txt" | - | - |
`ZipEntry` 代表 **ZIP 檔案內的一個條目 (檔案或目錄)**。它封裝了:
並非「建立檔案進入點」**,而是「描述一個將要寫入 ZIP 的條目元資料」。
**注意**:目錄條目通常**不寫入資料** (body 為空),僅建立目錄結構供解壓工具還原資料夾。
// 使用 try-with-resources 確保資源關閉
// 使用緩衝區提升 I/O 效能 (避免逐位元組 read/write)
public void zip(File src, File zipFile) throws IOException {
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile))) {
zipRecursive(out, src, "", src.getName()); // 修正:根目錄名稱納入 base
}
}
private void zipRecursive(ZipOutputStream out, File f, String base, String rootName) throws IOException {
String entryName = base.isEmpty() ? rootName : base + "/" + rootName;
if (f.isDirectory()) {
// 目錄條目 (不寫入資料)
out.putNextEntry(new ZipEntry(entryName + "/"));
out.closeEntry();
File[] children = f.listFiles();
if (children != null) {
for (File child : children) {
zipRecursive(out, child, entryName, child.getName());
}
}
} else {
// 檔案條目
out.putNextEntry(new ZipEntry(entryName));
try (FileInputStream in = new FileInputStream(f)) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
out.closeEntry();
}
}