繁体   English   中英

Java ZIP - 如何解压缩文件夹?

[英]Java ZIP - how to unzip folder?

是否有任何示例代码,如何将文件夹从 ZIP 部分解压缩到我想要的目录中? 我已将文件夹“FOLDER”中的所有文件读入字节数组,如何从其文件结构中重新创建?

我不确定你所说的部分是什么意思? 你的意思是在没有 API 帮助的情况下自己做吗?

如果您不介意使用一些开源库,那么有一个很酷的 API 叫做zip4J

它易于使用,我认为有很好的反馈。 看这个例子:

String source = "folder/source.zip";
String destination = "folder/source/";   

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

如果要解压的文件有密码,可以试试这个:

String source = "folder/source.zip";
String destination = "folder/source/";
String password = "password";

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

我希望这很有用。

这是我正在使用的代码。 根据您的需要更改 BUFFER_SIZE。

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public final class ZipUtils {

    private static final int BUFFER_SIZE = 4096;

    public static void extract(ZipInputStream zip, File target) throws IOException {
        try {
            ZipEntry entry;

            while ((entry = zip.getNextEntry()) != null) {
                File file = new File(target, entry.getName());

                if (!file.toPath().normalize().startsWith(target.toPath())) {
                    throw new IOException("Bad zip entry");
                }

                if (entry.isDirectory()) {
                    file.mkdirs();
                    continue;
                }

                byte[] buffer = new byte[BUFFER_SIZE];
                file.getParentFile().mkdirs();
                BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
                int count;

                while ((count = zip.read(buffer)) != -1) {
                    out.write(buffer, 0, count);
                }

                out.close();
            }
        } finally {
            zip.close();
        }
    }

}

最简洁、无库的 Java 7+ 变体:

public static void unzip(InputStream is, Path targetDir) throws IOException {
    targetDir = targetDir.toAbsolutePath();
    try (ZipInputStream zipIn = new ZipInputStream(is)) {
        for (ZipEntry ze; (ze = zipIn.getNextEntry()) != null; ) {
            Path resolvedPath = targetDir.resolve(ze.getName()).normalize();
            if (!resolvedPath.startsWith(targetDir)) {
                // see: https://snyk.io/research/zip-slip-vulnerability
                throw new RuntimeException("Entry with an illegal path: " 
                        + ze.getName());
            }
            if (ze.isDirectory()) {
                Files.createDirectories(resolvedPath);
            } else {
                Files.createDirectories(resolvedPath.getParent());
                Files.copy(zipIn, resolvedPath);
            }
        }
    }
}

两个分支都需要createDirectories ,因为 zip 文件并不总是包含所有父目录作为单独的条目,但可能仅包含它们以表示空目录。

该代码解决了ZIP-slip 漏洞,如果某些 ZIP 条目将 go 在targetDir之外,它将失败。 此类 ZIP 不是使用常用工具创建的,并且很可能是为了利用该漏洞而手工制作的。

同样可以使用 Ant 压缩库来实现。 它将保留文件夹结构。

Maven 依赖:-

<dependency>
    <groupId>org.apache.ant</groupId>
    <artifactId>ant-compress</artifactId>
    <version>1.2</version>
</dependency>

示例代码:-

Unzip unzipper = new Unzip();
unzipper.setSrc(theZIPFile);
unzipper.setDest(theTargetFolder);
unzipper.execute();

这是一个遵循更现代惯例的简单解决方案。 如果要解压缩较大的文件,您可能希望将缓冲区大小更改为更小。 这样您就不会将所有文件信息保留在内存中。

    public static void unzip(File source, String out) throws IOException {
    try (ZipInputStream zis = new ZipInputStream(new FileInputStream(source))) {

        ZipEntry entry = zis.getNextEntry();

        while (entry != null) {

            File file = new File(out, entry.getName());

            if (entry.isDirectory()) {
                file.mkdirs();
            } else {
                File parent = file.getParentFile();

                if (!parent.exists()) {
                    parent.mkdirs();
                }

                try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {

                    int bufferSize = Math.toIntExact(entry.getSize());
                    byte[] buffer = new byte[bufferSize > 0 ? bufferSize : 1];
                    int location;

                    while ((location = zis.read(buffer)) != -1) {
                        bos.write(buffer, 0, location);
                    }
                }
            }
            entry = zis.getNextEntry();
        }
    }
}

这是我用来解压缩具有多个目录的 zip 文件的代码。 没有使用外部库。

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class UnzipFile
{
  public static void main(String[] args) throws IOException
  {
    String fileZip = "src/main/resources/abcd/abc.zip";
    File destDir = new File("src/main/resources/abcd/abc");

    try (ZipFile file = new ZipFile(fileZip))
    {
      Enumeration<? extends ZipEntry> zipEntries = file.entries();
      while (zipEntries.hasMoreElements())
      {
        ZipEntry zipEntry = zipEntries.nextElement();
        File newFile = new File(destDir, zipEntry.getName());

        //create sub directories
        newFile.getParentFile().mkdirs();

        if (!zipEntry.isDirectory())
        {
          try (FileOutputStream outputStream = new FileOutputStream(newFile))
          {
            BufferedInputStream inputStream = new BufferedInputStream(file.getInputStream(zipEntry));
            while (inputStream.available() > 0)
            {
              outputStream.write(inputStream.read());
            }
            inputStream.close();
          }
        }

      }
    }
  }

}

这是基于这篇文章但经过重构(并使用Lombok )的更“现代”的完整代码:

import lombok.var;
import lombok.val;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipInputStream;

import static java.nio.file.Files.createDirectories;

public class UnZip
{
    public static void unZip(String sourceZipFile, String outputDirectory) throws IOException
    {
        val folder = new File(outputDirectory);
        createDirectories(folder.toPath());

        try (val zipInputStream = new ZipInputStream(new FileInputStream(sourceZipFile, Charset.forName("Cp437"))))
        {
            var nextEntry = zipInputStream.getNextEntry();

            while (nextEntry != null)
            {
                val fileName = nextEntry.getName();
                val newFile = new File(outputDirectory + File.separator + fileName);

                newFile.getParentFile().mkdirs();

                if(fileName.endsWith("/")){
                    newFile.mkdirs();
                } else {
                    writeFile(zipInputStream, newFile);
                }

                writeFile(zipInputStream, newFile);

                nextEntry = zipInputStream.getNextEntry();
            }

            zipInputStream.closeEntry();
        }
    }

    private static void writeFile(ZipInputStream inputStream, File file) throws IOException
    {
        val buffer = new byte[1024];
        file.createNewFile();
        try (val fileOutputStream = new FileOutputStream(file))
        {
            int length;
            while ((length = inputStream.read(buffer)) > 0)
            {
                fileOutputStream.write(buffer, 0, length);
            }
        }
    }
}

您应该从 zip 文件中获取所有条目:

Enumeration entries = zipFile.getEntries();

然后遍历这个枚举,从中获取ZipEntry ,检查它是否是一个目录,然后分别创建目录或只是提取一个文件。

使用其他库后,我偶然发现了这个: https://github.com/thrau/jarchivelib

远远优于。

Gradle: implementation group: 'org.rauschig', name: 'jarchivelib', version: '1.2.0'

import org.rauschig.jarchivelib.ArchiveFormat;
import org.rauschig.jarchivelib.Archiver;
import org.rauschig.jarchivelib.ArchiverFactory;
import org.rauschig.jarchivelib.CompressionType;

  public static void unzip(File zipFile, File targetDirectory) throws IOException, IllegalAccessException {
    Archiver archiver = ArchiverFactory.createArchiver(ArchiveFormat.ZIP);
    archiver.extract(zipFile, targetDirectory);
  }

  public static void unTarGz(File tarFile, File targetDirectory) throws IOException {
    Archiver archiver = ArchiverFactory.createArchiver(ArchiveFormat.TAR, CompressionType.GZIP);
    archiver.extract(tarFile, targetDirectory);
  }

其他库对于这个简单的任务来说太复杂了。 这就是为什么我喜欢这个库 - 2 行,完成。

根据 petrs 的回答,这是我现在使用的 kotlin 版本:

fun ZipInputStream.extractTo(target: File) = use { zip ->
    var entry: ZipEntry
    while (zip.nextEntry.also { entry = it ?: return } != null) {
        val file = File(target, entry.name)
        if (entry.isDirectory) {
            file.mkdirs()
        } else {
            file.parentFile.mkdirs()
            zip.copyTo(file.outputStream())
        }
    }
}

一个非常好的答案的替代方案,仍然是无 ZIP-slip 漏洞、无库和 Java 7+ 变体,但基于流:

public static void unzip(File zipFile, Path targetDir) throws IOException {
    // Normalize the path to get rid parent folder accesses
    Path targetRoot = targetDir.normalize();
    // Open the zip file as a FileSystem
    try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + zipFile.toURI()), Map.of())) {
        for (Path root : fs.getRootDirectories()) {
            try (Stream<Path> walk = Files.walk(root).filter(Files::isRegularFile)) {
                // For each file in the zip
                walk.forEach(
                    source -> {
                        // Compute the target path of the file in the destination folder
                        Path target = targetRoot.resolve(root.relativize(source).toString()).normalize();
                        if (target.startsWith(targetRoot)) {
                            // Only copy safe files
                            // see: https://snyk.io/research/zip-slip-vulnerability
                            try {
                                // Create parent folders if needed
                                Files.createDirectories(target.getParent());
                                // Copy the file to the destination
                                Files.copy(source, target);
                            } catch (IOException e) {
                                throw new UncheckedIOException(e);
                            }
                        }
                    });
            }
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM