如何在Java中提取tar文件?

69

我如何在Java中解压tar(或tar.gz或tar.bz2)文件?


skiphoppy,在我最初回答的2008年之后,Apache Commons Compress项目已经发布。你应该接受这个答案,这样它就会更加突出。 - erickson
8个回答

76
您可以使用Apache Commons Compress库来完成此操作。您可以从http://mvnrepository.com/artifact/org.apache.commons/commons-compress/1.2下载1.2版本。
以下是两种方法:一种是解压缩文件,另一种是解压tar文件。因此,对于一个文件<fileName>tar.gz,您需要先解压缩它,然后再解压tar文件。请注意,tar归档文件可能包含文件夹,这些文件夹也需要在本地文件系统上创建。
祝您使用愉快。
/** Untar an input file into an output file.

 * The output file is created in the output folder, having the same name
 * as the input file, minus the '.tar' extension. 
 * 
 * @param inputFile     the input .tar file
 * @param outputDir     the output directory file. 
 * @throws IOException 
 * @throws FileNotFoundException
 *  
 * @return  The {@link List} of {@link File}s with the untared content.
 * @throws ArchiveException 
 */
private static List<File> unTar(final File inputFile, final File outputDir) throws FileNotFoundException, IOException, ArchiveException {

    LOG.info(String.format("Untaring %s to dir %s.", inputFile.getAbsolutePath(), outputDir.getAbsolutePath()));

    final List<File> untaredFiles = new LinkedList<File>();
    final InputStream is = new FileInputStream(inputFile); 
    final TarArchiveInputStream debInputStream = (TarArchiveInputStream) new ArchiveStreamFactory().createArchiveInputStream("tar", is);
    TarArchiveEntry entry = null; 
    while ((entry = (TarArchiveEntry)debInputStream.getNextEntry()) != null) {
        final File outputFile = new File(outputDir, entry.getName());
        if (entry.isDirectory()) {
            LOG.info(String.format("Attempting to write output directory %s.", outputFile.getAbsolutePath()));
            if (!outputFile.exists()) {
                LOG.info(String.format("Attempting to create output directory %s.", outputFile.getAbsolutePath()));
                if (!outputFile.mkdirs()) {
                    throw new IllegalStateException(String.format("Couldn't create directory %s.", outputFile.getAbsolutePath()));
                }
            }
        } else {
            LOG.info(String.format("Creating output file %s.", outputFile.getAbsolutePath()));
            final OutputStream outputFileStream = new FileOutputStream(outputFile); 
            IOUtils.copy(debInputStream, outputFileStream);
            outputFileStream.close();
        }
        untaredFiles.add(outputFile);
    }
    debInputStream.close(); 

    return untaredFiles;
}

/**
 * Ungzip an input file into an output file.
 * <p>
 * The output file is created in the output folder, having the same name
 * as the input file, minus the '.gz' extension. 
 * 
 * @param inputFile     the input .gz file
 * @param outputDir     the output directory file. 
 * @throws IOException 
 * @throws FileNotFoundException
 *  
 * @return  The {@File} with the ungzipped content.
 */
private static File unGzip(final File inputFile, final File outputDir) throws FileNotFoundException, IOException {

    LOG.info(String.format("Ungzipping %s to dir %s.", inputFile.getAbsolutePath(), outputDir.getAbsolutePath()));

    final File outputFile = new File(outputDir, inputFile.getName().substring(0, inputFile.getName().length() - 3));

    final GZIPInputStream in = new GZIPInputStream(new FileInputStream(inputFile));
    final FileOutputStream out = new FileOutputStream(outputFile);

    IOUtils.copy(in, out);

    in.close();
    out.close();

    return outputFile;
}

1
你的例子是一个很好的开始,但我似乎遇到了一个问题: while ((entry = (TarArchiveEntry)debInputStream.getNextEntry()) != null). 问题在于当我通过外部框架(例如SAXBuilder)处理第一个文件时,输入流debInputStream被关闭,而depInputStream.getNextEntry()的第二次调用会抛出异常“输入缓冲区已关闭”。 - adranale
相关,具有类似的实现方法:如何使用Apache Commons解压缩TAR文件 - blong
3
当执行OutputStream outputFileStream = new FileOutputStream(outputFile)时,我遇到了“系统找不到指定的路径”的问题。解决方法是添加代码:File parent = outputFile.getParentFile(); if (!parent.exists()) parent.mkdirs(); 这样可以创建不存在的目录。 - Georgy Gobozov
1
警告!上述代码存在安全漏洞(zip文件可能包含相对路径,导致目标目录外的文件被覆盖)。请参阅https://snyk.io/research/zip-slip-vulnerability#what-action-should-you-take了解如何修复它。 - Lak
1
与其使用 inputFile.getName().length() - 3) 来避免硬编码扩展名长度,使用 inputFile.getName().lastIndexOf(".")) 更为妥当。 - martin_wun
显示剩余4条评论

22
注意:此功能后来通过单独的项目Apache Commons Compress发布,如另一个答案中所述。此答案已过时。

我没有直接使用过tar API,但是Ant中实现了tar和bzip2;你可以借用他们的实现,或者可能使用Ant来完成你需要的工作。

Gzip是Java SE的一部分(我猜Ant的实现遵循相同的模型)。

GZIPInputStream只是一个InputStream装饰器。您可以将FileInputStream包装在GZIPInputStream中,然后像使用任何InputStream一样使用它:

InputStream is = new GZIPInputStream(new FileInputStream(file));

(请注意,GZIPInputStream有其自己的内部缓冲区,因此将FileInputStream包装在BufferedInputStream中可能会降低性能。)


2
我正要告诉他关于GZIPInputStream。但这对他没有帮助,因为他仍然需要读取包含的.tar文件 :) - Johannes Schaub - litb
1
事实上,我已经知道GZIPInputStream了,这要归功于我在这里提出的另一个问题。但是我对tar API一无所知,我希望有一些可以集成处理gzip的东西,所以我不想通过说出我已经知道的所有内容来限制答案。 - skiphoppy
3
'ant'中捆绑的Apache类很好用。我每天都使用这些类:org.apache.tools.tar.TarEntry和org.apache.tools.tar.TarInputStream;代码与解压缩zip文件的代码非常相似。如果你想做Bzip2,请使用jaxlib。 - tucuxi
1
这里有一个(奇妙的)Ant / TarInputStream 示例的绝佳例子。https://code.google.com/p/jtar/顺便赞一个使用ant libs。 - jsh
另一种用于BZIP2的方法--https://dev59.com/vHE95IYBdhLWcg3wadKq - jsh

15
Archiver archiver = ArchiverFactory.createArchiver("tar", "gz");
archiver.extract(archiveFile, destDir);

依赖:

 <dependency>
        <groupId>org.rauschig</groupId>
        <artifactId>jarchivelib</artifactId>
        <version>0.5.0</version>
</dependency>

这个是目前为止最好的 - 两行。嘭。 - Nicholas DiPiazza

13

8

我刚刚尝试了一些建议的库(TrueZip、Apache Compress),但都没有成功。

这里有一个使用Apache Commons VFS的示例:

FileSystemManager fsManager = VFS.getManager();
FileObject archive = fsManager.resolveFile("tgz:file://" + fileName);

// List the children of the archive file
FileObject[] children = archive.getChildren();
System.out.println("Children of " + archive.getName().getURI()+" are ");
for (int i = 0; i < children.length; i++) {
    FileObject fo = children[i];
    System.out.println(fo.getName().getBaseName());
    if (fo.isReadable() && fo.getType() == FileType.FILE
        && fo.getName().getExtension().equals("nxml")) {
        FileContent fc = fo.getContent();
        InputStream is = fc.getInputStream();
    }
}

这是Maven的依赖项:

    <dependency>
      <groupId>commons-vfs</groupId>
      <artifactId>commons-vfs</artifactId>
      <version>1.0</version>
    </dependency>

6

1
Apache Commons Compress API具有tar支持,并且最初基于上述ICE tar包,我相信: http://commons.apache.org/compress/ - Jörg
2
我的测试显示,在五个竞争者(ice、compress、ant、xeus + vfs)中,ICE tar 是最快的,而 Commons Compress 排名第二...然而,就完整解压所有条目和保持归档条目原始文件名而言,ICE tar 似乎稍微不太可靠。 - Jörg

5
这是基于 Dan Borza 的 早期答案,使用 Apache Commons Compress 和 Java NIO(即 Path 而不是 File)的版本。它还将解压缩和解 tar 包放在一个流中,因此没有中间文件的创建。
public static void unTarGz( Path pathInput, Path pathOutput ) throws IOException {
    TarArchiveInputStream tararchiveinputstream =
        new TarArchiveInputStream(
            new GzipCompressorInputStream(
                new BufferedInputStream( Files.newInputStream( pathInput ) ) ) );

    ArchiveEntry archiveentry = null;
    while( (archiveentry = tararchiveinputstream.getNextEntry()) != null ) {
        Path pathEntryOutput = pathOutput.resolve( archiveentry.getName() );
        if( archiveentry.isDirectory() ) {
            if( !Files.exists( pathEntryOutput ) )
                Files.createDirectory( pathEntryOutput );
        }
        else
            Files.copy( tararchiveinputstream, pathEntryOutput );
    }

    tararchiveinputstream.close();
}

Files.copy 不是将整个归档复制到一个文件中吗? - Yann
好的,它之所以能够正常工作是因为 TarArchiveInputStream "知道归档文件中当前条目的边界" - Yann

4

您考虑使用这个API来处理tar文件,Ant内置的另一个用于BZIP2,以及标准的GZIP解压缩。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接