什么是好的 Java 库来压缩/解压缩文件?[已结束]

What is a good Java library to zip/unzip files? [closed]

提问人:pathikrit 提问时间:2/17/2012 最后编辑:pathikrit 更新时间:5/4/2023 访问量:260313

问:


我们不允许向读者、工具、软件库等寻求推荐的问题。您可以编辑问题,以便用事实和引文来回答。

7年前关闭。

我查看了 JDK 和 Apache 压缩库附带的默认 Zip 库,我对它们不满意,原因有 3 个:

  1. 它们臃肿且 API 设计不佳。我必须编写 50 行样板字节数组输出、zip 输入、文件输出流并关闭相关流并捕获异常并自行移动字节缓冲区?为什么我不能有一个简单的 API 看起来像这样并且可以正常工作?Zipper.unzip(InputStream zipFile, File targetDirectory, String password = null)Zipper.zip(File targetDirectory, String password = null)

  2. 似乎压缩解压缩会破坏文件元数据并且密码处理已损坏。

  3. 另外,与我使用 UNIX 获得的命令行 zip 工具相比,我尝试的所有库都慢了 2-3 倍?

对我来说,(2)和(3)是次要的点,但我真的想要一个经过测试的库,有一个单行界面。

zip 解压缩 java

评论

15赞 Edward Thomson 2/17/2012
至于#1,这是因为不是每个人都只是简单地将文件解压缩到目录。如果你总是使用相同的模式,为什么不直接编写一个实用程序类来包装其他模式之一,并做你需要它做的事情,然后就使用它呢?
22赞 Zak 8/16/2013
@EdwardThomson,因为使用库比编写代码、测试代码和维护代码更容易。
16赞 pathikrit 8/24/2013
@EdwardThomson:你的论点是无效的。查看 Python zip API:docs.python.org/3/library/zipfile。您需要 1 行代码来压缩或解压缩文件。API 应该很好地处理常见情况,除了压缩或解压缩之外,我想不出 zip API 的任何用例。
8赞 Edward Thomson 8/24/2013
@wrick:压缩文件或解压缩文件是压缩或解压缩流的特例。如果你的 API 不允许我向它写入流,而是让我将流写入文件,以便我可以将其提供给你的 API,那么你的 API 就已经损坏了。
61赞 ArtOfWarfare 11/20/2013
@EdwardThomson - 很好,所以让库同时支持文件和流。这是在浪费每个人的时间——我的、你的、提问者以及所有其他 Google 员工,他们都会偶然发现我们每个人都必须实现自己的 Zip 实用程序。就像有 DRY 一样,也有 DROP - 不要重复其他人。

答:

0赞 wemu 2/17/2012 #1

你看过 http://commons.apache.org/vfs/ 吗?它声称可以为您简化很多事情。但我从来没有在项目中使用过它。

我也不知道 JDK 或 Apache 压缩以外的 Java 原生压缩库。

我记得有一次我们从 Apache Ant 中扯掉了一些功能——它们内置了很多用于压缩/解压缩的实用程序。

VFS 的示例代码如下所示:

File zipFile = ...;
File outputDir = ...;
FileSystemManager fsm = VFS.getManager();
URI zip = zipFile.toURI();
FileObject packFileObject = fsm.resolveFile(packLocation.toString());
FileObject to = fsm.toFileObject(destDir);
FileObject zipFS;
try {
    zipFS = fsm.createFileSystem(packFileObject);
    fsm.toFileObject(outputDir).copyFrom(zipFS, new AllFileSelector());
} finally {
    zipFS.close();
}

评论

1赞 T.J. Crowder 2/17/2012
看起来 VFS 内容本身对 zip 文件的支持非常有限:commons.apache.org/vfs/filesystems.html
47赞 Bashir Beikzadeh 2/17/2012 #2

仅使用 JDK 提取 zip 文件及其所有子文件夹:

private void extractFolder(String zipFile,String extractFolder) 
{
    try
    {
        int BUFFER = 2048;
        File file = new File(zipFile);

        ZipFile zip = new ZipFile(file);
        String newPath = extractFolder;

        new File(newPath).mkdir();
        Enumeration zipFileEntries = zip.entries();

        // Process each entry
        while (zipFileEntries.hasMoreElements())
        {
            // grab a zip file entry
            ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();
            String currentEntry = entry.getName();

            File destFile = new File(newPath, currentEntry);
            //destFile = new File(newPath, destFile.getName());
            File destinationParent = destFile.getParentFile();

            // create the parent directory structure if needed
            destinationParent.mkdirs();

            if (!entry.isDirectory())
            {
                BufferedInputStream is = new BufferedInputStream(zip
                .getInputStream(entry));
                int currentByte;
                // establish buffer for writing file
                byte data[] = new byte[BUFFER];

                // write the current file to disk
                FileOutputStream fos = new FileOutputStream(destFile);
                BufferedOutputStream dest = new BufferedOutputStream(fos,
                BUFFER);

                // read and write until last byte is encountered
                while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
                    dest.write(data, 0, currentByte);
                }
                dest.flush();
                dest.close();
                is.close();
            }


        }
    }
    catch (Exception e) 
    {
        Log("ERROR: "+e.getMessage());
    }

}

Zip 文件及其所有子文件夹:

 private void addFolderToZip(File folder, ZipOutputStream zip, String baseName) throws IOException {
    File[] files = folder.listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            addFolderToZip(file, zip, baseName);
        } else {
            String name = file.getAbsolutePath().substring(baseName.length());
            ZipEntry zipEntry = new ZipEntry(name);
            zip.putNextEntry(zipEntry);
            IOUtils.copy(new FileInputStream(file), zip);
            zip.closeEntry();
        }
    }
}

评论

9赞 8/24/2012
关闭的调用至少应该在“最终”块内。异常没有得到很好的处理。->我想这就是 OP 要求使用的部分原因。
1赞 Renato 4/19/2020
此代码不保留文件属性和权限...如果您使用类似的东西来解压缩可运行的应用程序,请准备好应对有关文件权限的奇怪错误。这让我头痛了一个星期。
0赞 user1194528 10/24/2023
代码是 Zip Slip 易受攻击的漏洞
8赞 Michael 2/17/2012 #3

一个非常好的项目是TrueZip

TrueZIP 是一个基于 Java 的虚拟文件系统 (VFS) 插件框架,它提供对归档文件的透明访问,就好像它们只是普通目录一样

例如(来自网站):

File file = new TFile("archive.tar.gz/README.TXT");
OutputStream out = new TFileOutputStream(file);
try {
   // Write archive entry contents here.
   ...
} finally {
   out.close();
}

评论

0赞 pathikrit 2/17/2012
该库看起来不错 - 仍然不清楚如何简单地解压缩给定 zipinputstream/file/path 的 zip 文件。
1赞 Teo Klestrup Röijezon 6/6/2013
TrueZIP似乎不能很好地处理从流中读取数据。
5赞 peterh 3/18/2014
这和你在Java 7中可以做的事情大体上不是一样的吗?(查看 ZipFileSystemProvider)。
1赞 iuzuz 10/9/2018
@peterh:标准的 JDK ZipFileSystemProvider 将是一个很好的答案。只有少数人将其视为评论。
98赞 Geoffrey De Smet 12/17/2012 #4

在 Java 8 中,使用 Apache Commons-IOIOUtils 可以做到这一点:

try (java.util.zip.ZipFile zipFile = new ZipFile(file)) {
  Enumeration<? extends ZipEntry> entries = zipFile.entries();
  while (entries.hasMoreElements()) {
    ZipEntry entry = entries.nextElement();
    File entryDestination = new File(outputDir,  entry.getName());
    if (entry.isDirectory()) {
        entryDestination.mkdirs();
    } else {
        entryDestination.getParentFile().mkdirs();
        try (InputStream in = zipFile.getInputStream(entry);
             OutputStream out = new FileOutputStream(entryDestination)) {
            IOUtils.copy(in, out);
        }
    }
  }
}

它仍然是一些样板代码,但它只有 1 个非外来依赖项:Commons-IO

在 Java 11 及更高版本中,可能会有更好的选择,请参阅 ZhekaKozlov 的评论。

评论

1赞 Randy 3/19/2014
@VitalySazanovich您指的是 Java 7 ZipEntry。
4赞 Juan Mendez 7/22/2015
为什么不是 IOUtils.closeQuietly(out)?
2赞 vadipp 6/14/2016
@JuanMendez,因为如果关闭时出现错误,则无法确定文件是否完整且正确地保存。但除此之外,正常情况也不会受到伤害。close()
4赞 Marcono1234 4/28/2019
此解决方案容易受到 ZipSlip 的攻击(zip4j 也会受到影响)
4赞 ZhekaKozlov 2/9/2021
在 Java 9+ 中,您不再需要 IOUtils。只需写.zipFile.getInputStream(entry).transferTo(outputStream)
340赞 user2003470 2/2/2013 #5

我知道它已经晚了,有很多答案,但这个 zip4j 是我用过的最好的压缩库之一。它很简单(没有锅炉代码),可以轻松处理受密码保护的文件。

import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.core.ZipFile;


public static void unzip(){
    String source = "some/compressed/file.zip";
    String destination = "some/destination/folder";
    String password = "password";

    try {
         ZipFile zipFile = new ZipFile(source);
         if (zipFile.isEncrypted()) {
            zipFile.setPassword(password);
         }
         zipFile.extractAll(destination);
    } catch (ZipException e) {
        e.printStackTrace();
    }
}

Maven 依赖项如下:

<dependency>
    <groupId>net.lingala.zip4j</groupId>
    <artifactId>zip4j</artifactId>
    <version>1.3.2</version>
</dependency>

评论

1赞 BorisHrenPopadesh 3/7/2019
当您解压缩资源文件夹中的文件时,也可能会出现问题。你拿一个这样的 zip 文件,但这样文件就不会被解压缩,它会导致 EOFException 或 MALFORMED。这就是全部,因为当您使用 maven 时,您必须关闭 maven 资源插件 <configuration> <nonFilteredFileExtensions> <nonFilteredFileExtension>zip</nonFilteredFileExtension> </nonFilteredFileExtensions> ...code new File(getClass().getResource(zipFileName).getPath());
0赞 KuhakuPixel 2/25/2023
对于版本 2.11.3 to up,请使用 refimport net.lingala.zip4j.ZipFile
0赞 Daniel Hári 5/21/2023
无法从 InputStream 解压缩,只能从 FIle 解压缩。例如,在使用资源时,这是个问题。
25赞 toomasr 5/1/2013 #6

您可以查看的另一个选项是 zt-zip,可从 Maven 中心和项目页面获得 https://github.com/zeroturnaround/zt-zip

它具有标准的打包和解包功能(在流和文件系统上)+ 许多辅助方法,用于测试存档中的文件或添加/删除条目。

评论

0赞 Daniel Hári 5/21/2023
这是最好的,因为它也可以处理 InputStream。
4赞 Henrik Aasted Sørensen 9/20/2013 #7

另一种选择是 JZlib。根据我的经验,它不像 zip4J 那样“以文件为中心”,所以如果你需要处理内存中的 blob 而不是文件,你可能想看看它。

0赞 user1491819 2/15/2015 #8

这里有一个以递归方式压缩和解压缩文件的完整示例:http://developer-tips.hubpages.com/hub/Zipping-and-Unzipping-Nested-Directories-in-Java-using-Apache-Commons-Compress

21赞 Minhas Kamal 3/18/2015 #9

使用 zip4j 压缩/解压缩文件夹/文件的完整实现


将此依赖项添加到生成管理器。或者,从此处下载最新的 JAR 文件并将其添加到您的项目构建路径中。波纹管可以压缩和提取任何有或没有密码保护的文件或文件夹-class

import java.io.File;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
import net.lingala.zip4j.core.ZipFile;  

public class Compressor {
    public static void zip (String targetPath, String destinationFilePath, String password) {
        try {
            ZipParameters parameters = new ZipParameters();
            parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
            parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);

            if (password.length() > 0) {
                parameters.setEncryptFiles(true);
                parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_AES);
                parameters.setAesKeyStrength(Zip4jConstants.AES_STRENGTH_256);
                parameters.setPassword(password);
            }
                
            ZipFile zipFile = new ZipFile(destinationFilePath);
                
            File targetFile = new File(targetPath);
            if (targetFile.isFile()) {
                zipFile.addFile(targetFile, parameters);
            } else if (targetFile.isDirectory()) {
                zipFile.addFolder(targetFile, parameters);
            } else {
                //neither file nor directory
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
        
    public static void unzip(String targetZipFilePath, String destinationFolderPath, String password) {
        try {
            ZipFile zipFile = new ZipFile(targetZipFilePath);
            if (zipFile.isEncrypted()) {
                zipFile.setPassword(password);
            }
            zipFile.extractAll(destinationFolderPath);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**/ /// for test
    public static void main(String[] args) {
        
        String targetPath = "target\\file\\or\\folder\\path";
        String zipFilePath = "zip\\file\\Path"; 
        String unzippedFolderPath = "destination\\folder\\path";
        String password = "your_password"; // keep it EMPTY<""> for applying no password protection
            
        Compressor.zip(targetPath, zipFilePath, password);
        Compressor.unzip(zipFilePath, unzippedFolderPath, password);
    }/**/
}

有关更详细的用法,请参阅此处

评论

2赞 Jonty800 3/27/2018
一个很好的答案和图书馆。在此库上提取 1868 个文件需要 ~15 秒,而使用 ZipInputStream 时需要 20+ 分钟(出于某种原因)
0赞 Felix S 5/6/2021
@Jonty800 有了这样的性能差异,你也许应该重新审视一下你的实现。如果您不缓冲流,并且每个字节都是直接从设备读取/写入的,那么您将获得这样的性能差异。我刚刚提取了 17588 个文件,总大小为 1.8 GB,zip4j 花了 64 秒,而缓冲的标准库实现花了 39 秒。话虽如此,一个朴素的 BufferedOutputStream 实现大约需要 5 分钟。