在Java中压缩和解压缩

2023/05/29

1. 概述

在这个快速教程中,我们将学习如何将文件压缩到存档中以及如何解压缩存档,所有这些都使用Java提供的核心库。

这些核心库是java.util.zip包的一部分,我们可以在其中找到所有与压缩和解压缩相关的实用程序。

2. 压缩文件

首先,让我们看一个简单的操作,压缩单个文件。

例如,我们将一个名为test1.txt的文件压缩到名为compressed.zip的存档中。

当然,我们首先从磁盘访问文件:

public class ZipFile {

    public static void main(String[] args) throws IOException {
        String sourceFile = "test1.txt";
        FileOutputStream fos = new FileOutputStream("compressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);

        File fileToZip = new File(sourceFile);
        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);
        }

        zipOut.close();
        fis.close();
        fos.close();
    }
}

3. 压缩多个文件

接下来,让我们看看如何将多个文件压缩到一个zip文件中。我们将test1.txt和test2.txt压缩到multiCompressed.zip中:

public class ZipMultipleFiles {

    public static void main(String[] args) throws IOException {
        String file1 = "src/main/resources/zipTest/test1.txt";
        String file2 = "src/main/resources/zipTest/test2.txt";
        final List<String> srcFiles = Arrays.asList(file1, file2);

        final FileOutputStream fos = new FileOutputStream(Paths.get(file1).getParent().toAbsolutePath() + "/compressed.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();
    }
}

4. 压缩目录

现在,让我们讨论如何压缩整个目录。在这里,我们将zipTest文件夹压缩到dirCompressed.zip文件中:

public class ZipDirectory {
    public static void main(String[] args) throws IOException {
        String sourceFile = "zipTest";
        FileOutputStream fos = new FileOutputStream("dirCompressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);

        File fileToZip = new File(sourceFile);
        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("/")) {
                zipOut.putNextEntry(new ZipEntry(fileName));
                zipOut.closeEntry();
            } else {
                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();
    }
}

注意:

  • 为了压缩子目录,我们递归地遍历它们。
  • 每次我们找到一个目录时,我们将其名称附加到后代的ZipEntry名称以保存层次结构。
  • 我们还为每个空目录创建一个目录条目。

5. 将新文件附加到Zip文件

接下来,我们将向现有的zip文件中添加一个文件。例如,让我们将file3.txt添加到compressed.zip中:

String file3 = "src/main/resources/zipTest/file3.txt";
Map<String, String> env = new HashMap<>();
env.put("create", "true");

Path path = Paths.get(Paths.get(file3).getParent() + "/compressed.zip");
URI uri = URI.create("jar:" + path.toUri());

try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {
    Path nf = fs.getPath("newFile3.txt");
    Files.write(nf, Files.readAllBytes(Paths.get(file3)), StandardOpenOption.CREATE);
}

简而言之,我们使用FileSystems类提供的.newFileSystem()挂载了zip文件,该类从JDK 1.7开始可用。然后,我们在压缩文件夹中创建了一个newFile3.txt并添加了file3.txt中的所有内容。

6. 解压缩存档

现在,让我们解压缩存档并提取其内容。

对于这个例子,我们将把compressed.zip解压到一个名为unzipTest的新文件夹中:

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) {
            // ...
        }

        zis.closeEntry();
        zis.close();
    }
}

在while循环中,我们将遍历每个ZipEntry并首先检查它是否是一个目录。如果是,那么我们将使用mkdirs()方法创建目录;否则,我们将继续创建文件:

while (zipEntry != null) {
    File newFile = newFile(destDir, zipEntry);
    if (zipEntry.isDirectory()) {
        if (!newFile.isDirectory() && !newFile.mkdirs()) {
            throw new IOException("Failed to create directory " + newFile);
        }
    } else {
        // fix for Windows-created archives
        File parent = newFile.getParentFile();
        if (!parent.isDirectory() && !parent.mkdirs()) {
            throw new IOException("Failed to create directory " + parent);
        }

        // write file content
        FileOutputStream fos = new FileOutputStream(newFile);
        int len;
        while ((len = zis.read(buffer)) > 0) {
            fos.write(buffer, 0, len);
        }
        fos.close();
    }
    zipEntry = zis.getNextEntry();
}

这里需要注意的是,在else分支上,我们还检查文件的父目录是否存在。这对于在Windows上创建的存档是必需的,其中根目录在zip文件中没有相应的条目。

另一个关键点在newFile()方法中:

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

7. 总结

在本文中,我们说明了如何使用Java库来压缩和解压缩文件。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章