首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 解壓縮 ZIP 文件完整代碼:ZipInputStream 逐條目讀寫、自動建立目錄、中文亂碼說明

     編輯:Java 相關知識

核心代碼

import java.io.*;
import java.util.zip.*;
import java.nio.file.*;

public class UnzipUtil {
    public static void unzip(String zipPath, String destDir) throws IOException {
        Path dest = Paths.get(destDir).toAbsolutePath().normalize();
        Files.createDirectories(dest);
        
        try (ZipInputStream zis = new ZipInputStream(
                new BufferedInputStream(new FileInputStream(zipPath)))) {
            
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                Path target = dest.resolve(entry.getName()).normalize();
                
                // 防止 Zip Slip 攻擊 (目錄遍歷)
                if (!target.startsWith(dest)) {
                    throw new SecurityException("Zip Slip 嘗試: " + entry.getName());
                }
                
                if (entry.isDirectory()) {
                    Files.createDirectories(target);
                } else {
                    Files.createDirectories(target.getParent());
                    try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(target))) {
                        byte[] buffer = new byte[8192];
                        int len;
                        while ((len = zis.read(buffer)) > 0) {
                            out.write(buffer, 0, len);
                        }
                    }
                }
                zis.closeEntry();
            }
        }
    }
}

關鍵點解析

  1. ZipInputStream:流式讀取,不將整個 ZIP 載入記憶體,適合大文件。
  2. 目錄遍歷防護:target.startsWith(dest) 確保解壓路徑不逃逸目的目錄 (CVE-2018-1000000 類漏洞)。
  3. 緩衝區大小:8KB 平衡記憶體與系統調用次數。
  4. 中文文件名亂碼:JDK 7 及之前預設用 IBM437/CP437 解碼條目名,導致中文亂碼。JDK 8+ 可透過系統屬性 -Dsun.zip.encoding=UTF-8 或使用 Apache Commons Compress ZipFile 指定編碼。

中文亂碼完整解決方案 (Apache Commons Compress)

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-compress</artifactId>
    <version>1.26.0</version>
</dependency>

public static void unzipWithEncoding(String zipPath, String destDir, String encoding) throws IOException {
    try (org.apache.commons.compress.archivers.zip.ZipFile zipFile = 
            new org.apache.commons.compress.archivers.zip.ZipFile(zipPath, encoding)) {
        
        for (ZipArchiveEntry entry : zipFile) {
            Path target = Paths.get(destDir).resolve(entry.getName()).normalize();
            if (!target.startsWith(Paths.get(destDir).normalize())) {
                throw new SecurityException("Zip Slip: " + entry.getName());
            }
            if (entry.isDirectory()) {
                Files.createDirectories(target);
            } else {
                Files.createDirectories(target.getParent());
                try (InputStream in = zipFile.getInputStream(entry);
                     OutputStream out = Files.newOutputStream(target)) {
                    in.transferTo(out); // Java 9+
                }
            }
        }
    }
}

進階:並行解壓 (大量小文件)

// 使用 ForkJoinPool 或自定義線程池並行處理條目
// 注意:ZipInputStream 非線程安全,需先收集條目列表再並行
List entries = new ArrayList<>();
try (ZipInputStream zis = new ZipInputStream(...)) {
    ZipEntry e;
    while ((e = zis.getNextEntry()) != null) entries.add(e);
}
entries.parallelStream().forEach(entry -> { /* 解壓單個條目 */ });
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved