Java解压缩文件

本文我们讨论如何解压缩文件。java核心库提供了一些实用工具,java.util.zip包中可以找到所有压缩和解压相关工具。

1. 压缩文件

包括单个文件、多个文件以及压缩目录。

1.1. 单个文件

首先看个简单操作————压缩单个文件,即把源文件test1.txt压缩为归档文件compressed.zip。
当然首先从磁盘读文件,test1.txt在gradle项目的resouces目录下:

public class ZipDemo {
    public static void main(String[] args) throws IOException, URISyntaxException {
        String sourceFile = "test1.txt";
        URL resource = ZipDemo.class.getClassLoader().getResource(sourceFile);
        File fileToZip = new File(resource.toURI());
        FileInputStream fis = new FileInputStream(fileToZip);
        ZipEntry zipEntry = new ZipEntry(fileToZip.getName());

        Path targget = Paths.get(fileToZip.getParent(), "compressed.zip");

        FileOutputStream fos = new FileOutputStream(targget.toFile());
        ZipOutputStream zipOut = new ZipOutputStream(fos);
        zipOut.putNextEntry(zipEntry);

        byte[] bytes = new byte[1024];
        int length;
        while((length = fis.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        zipOut.close();
        fis.close();
        fos.close();
    }
}

生成的目标归档文件在相同目录下,build中。

1.2. 压缩多个文件

接下来看如何压缩多个文件:

public class ZipMultipleFiles {
    public static void main(String[] args) throws IOException {
        List<String> srcFiles = Arrays.asList("test1.txt", "test2.txt");
        FileOutputStream fos = new FileOutputStream("multiCompressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);
        for (String srcFile : srcFiles) {
            File fileToZip = new File(srcFile);
            FileInputStream fis = new FileInputStream(fileToZip);
            ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
            zipOut.putNextEntry(zipEntry);
 
            byte[] bytes = new byte[1024];
            int length;
            while((length = fis.read(bytes)) >= 0) {
                zipOut.write(bytes, 0, length);
            }
            fis.close();
        }
        zipOut.close();
        fos.close();
    }
}

1.3. 压缩目录

让我们讨论如何压缩整个目录:

public class ZipDirectory {
    public static void main(String[] args) throws IOException {
        String sourceDir = "zipTest";
        FileOutputStream fos = new FileOutputStream("dirCompressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);
        File fileToZip = new File(sourceDir);
 
        zipFile(fileToZip, fileToZip.getName(), zipOut);
        zipOut.close();
        fos.close();
    }
 
    private static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException {
        if (fileToZip.isHidden()) {
            return;
        }

        if (fileToZip.isDirectory()) {
            if (fileName.endsWith(File.pathSeparator)) {
                 fileName = fileName + File.pathSeparator;
            }
            zipOut.putNextEntry(new ZipEntry(fileName));
            zipOut.closeEntry();

            File[] children = fileToZip.listFiles();
            for (File childFile : children) {
                zipFile(childFile, fileName + childFile.getName(), zipOut);
            }
            return;
        }
        FileInputStream fis = new FileInputStream(fileToZip);
        ZipEntry zipEntry = new ZipEntry(fileName);
        zipOut.putNextEntry(zipEntry);
        byte[] bytes = new byte[1024];
        int length;
        while ((length = fis.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        fis.close();
    }
}

可能有子目录,这里需要递归进行迭代。
每次发现目录,在子ZipEntity中增加其名称用于保存层次。
我们也给空目录创建目录项。

2. 解压

解压 compressed.zip 至新的文件夹nuzipTest:

public class UnzipFile {
    public static void main(String[] args) throws IOException {
        String fileZip = "src/main/resources/unzipTest/compressed.zip";
        File destDir = new File("src/main/resources/unzipTest");
        byte[] buffer = new byte[1024];
        ZipInputStream zis = new ZipInputStream(new FileInputStream(fileZip));
        ZipEntry zipEntry = zis.getNextEntry();
        while (zipEntry != null) {
            File newFile = newFile(destDir, zipEntry);
            FileOutputStream fos = new FileOutputStream(newFile);
            int len;
            while ((len = zis.read(buffer)) > 0) {
                fos.write(buffer, 0, len);
            }
            fos.close();
            zipEntry = zis.getNextEntry();
        }
        zis.closeEntry();
        zis.close();
    }
     
    public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
        File destFile = new File(destinationDir, zipEntry.getName());
         
        String destDirPath = destinationDir.getCanonicalPath();
        String destFilePath = destFile.getCanonicalPath();
         
        if (!destFilePath.startsWith(destDirPath + File.separator)) {
            throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
        }
         
        return destFile;
    }
}

另一个关键点是在newFile()方法中防止将文件写入目标文件夹外的文件系统,该问题称为Zip Slip漏洞。

3. 总结

本文介绍如何利用Java核心库实现解压缩文件功能,读者也可以使用第三方库Zip4j尝试更多功能。