使用Java创建tar文件

28

我想使用Java以编程方式将文件夹压缩为tar文件。我认为一定有开源或库可以做到这一点,但我找不到这样的方法。

另外,我能否制作一个zip文件,然后将其扩展名更改为.tar?

是否有人可以建议一个库来完成这个任务?谢谢!


5
将一个zip文件重命名为.tar并不会有任何生产性的效果,这只是在欺骗文件内容。 - Joachim Sauer
7
这句话的意思是:“这不是一个重复的问题,一个问题是询问如何解压tar文件,而这个问题则是询问如何创建一个tar文件。”为了让这句话更加通俗易懂,我会进行以下翻译:“这不是一个重复的问题,一个问题是关于如何提取tar文件,而这个问题则是关于如何创建tar文件。” - Marcelo
Tar没有被压缩。你想要压缩还是tar? - user unknown
你知道改变文件扩展名并不会实际改变其内容,对吧? - njzk2
很可能提问者想要压缩并打包成tar格式。tar归档文件通常会使用外部压缩程序进行压缩(例如gzip,bzip2,xz等等)- 这是非常普遍的,以至于人们经常将tar+gzip视为一个操作。 - Lassi
4个回答

32

我会看一下Apache Commons Compress

此示例页面的中间部分,有一个展示tar示例的例子。

TarArchiveEntry entry = new TarArchiveEntry(name);
entry.setSize(size);
tarOutput.putArchiveEntry(entry);
tarOutput.write(contentOfEntry);
tarOutput.closeArchiveEntry();

18

您可以使用 jtar - Java Tar库

引用自他们的网站:

JTar是一个简单的Java Tar库,提供了一种使用IO流创建和读取tar文件的简便方式。该API非常易于使用,并类似于java.util.zip包。

以下为其网站提供的示例:

   // Output file stream
   FileOutputStream dest = new FileOutputStream( "c:/test/test.tar" );

   // Create a TarOutputStream
   TarOutputStream out = new TarOutputStream( new BufferedOutputStream( dest ) );

   // Files to tar
   File[] filesToTar=new File[2];
   filesToTar[0]=new File("c:/test/myfile1.txt");
   filesToTar[1]=new File("c:/test/myfile2.txt");

   for(File f:filesToTar){
      out.putNextEntry(new TarEntry(f, f.getName()));
      BufferedInputStream origin = new BufferedInputStream(new FileInputStream( f ));

      int count;
      byte data[] = new byte[2048];
      while((count = origin.read(data)) != -1) {
         out.write(data, 0, count);
      }

      out.flush();
      origin.close();
   }

   out.close();

这对于 .txt 文件很好用,但是当我想要创建一个文件夹的 .tar 文件时,你能帮忙吗? - Nikita Shah
我遇到了运行时错误: java.lang.NoClassDefFoundError: Failed resolution of: Ljava/nio/file/attribute/PosixFilePermission;请帮帮我,谢谢! - Beatrice Lin
nikita-shah:请查看我的解决方案。如果您有文件列表,它可以帮助处理文件夹。最终使用DirectoryStream<Path> stream = Files.newDirectoryStream(dirpath)迭代文件夹中的文件。 - aprodan

8

Tar归档文件确实没有压缩,这是准确的吗?我知道Tar的直接目的不是压缩,但让我们看看这种情况:文件系统使用每个大小为4KB的块。现在你有任意文件,对于每个“size mod 4KB!= 0”都是真的。因此,每个文件浪费<4KB的存储空间,因为它没有填满最后一个块。文件越多,整体浪费就越大,这可能变得很重要。续下评论... - user573215
将所有文件合并为一个Tar文件时,整个Tar文件中未使用的块大小浪费小于4KB。因此,可能存在类似隐式压缩或更有效的存储利用方式。该论文基于这样一种假设:每个单独文件的最后一个块的浪费部分将被丢弃,不会复制到Tar文件中。这一假设和随后的想法是否正确? - user573215
1
维基百科将压缩定义为:在信号处理中,数据压缩[...]涉及使用比原始表示少的位对信息进行编码因此,您不是在压缩存档中的文件,而是在删除文件系统应用于它们以将它们存储到磁盘中的填充。换句话说,“原始表示”具有固有长度,如果文件系统将填充应用于4Kb,则不会更改长度。实际上,许多文件系统甚至不这样做(请参见尾部打包)。 - Germano Rizzo

5

我已经编写了以下代码来解决这个问题。这段代码会检查要合并的文件是否已经存在于tar文件中,并更新该条目。如果不存在,则将其附加到存档结尾。

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;

public class TarUpdater {

        private static final int buffersize = 8048;

        public static void updateFile(File tarFile, File[] flist) throws IOException {
            // get a temp file
            File tempFile = File.createTempFile(tarFile.getName(), null);
            // delete it, otherwise you cannot rename your existing tar to it.
            if (tempFile.exists()) {
                tempFile.delete();
            }

            if (!tarFile.exists()) {
                tarFile.createNewFile();
            }

            boolean renameOk = tarFile.renameTo(tempFile);
            if (!renameOk) {
                throw new RuntimeException(
                        "could not rename the file " + tarFile.getAbsolutePath() + " to " + tempFile.getAbsolutePath());
            }
            byte[] buf = new byte[buffersize];

            TarArchiveInputStream tin = new TarArchiveInputStream(new FileInputStream(tempFile));

            OutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(tarFile.toPath()));
            TarArchiveOutputStream tos = new TarArchiveOutputStream(outputStream);
            tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);

            //read  from previous  version of  tar  file
            ArchiveEntry entry = tin.getNextEntry();
            while (entry != null) {//previous  file  have entries
                String name = entry.getName();
                boolean notInFiles = true;
                for (File f : flist) {
                    if (f.getName().equals(name)) {
                        notInFiles = false;
                        break;
                    }
                }
                if (notInFiles) {
                    // Add TAR entry to output stream.
                    if (!entry.isDirectory()) {
                        tos.putArchiveEntry(new TarArchiveEntry(name));
                        // Transfer bytes from the TAR file to the output file
                        int len;
                        while ((len = tin.read(buf)) > 0) {
                            tos.write(buf, 0, len);
                        }
                    }
                }
                entry = tin.getNextEntry();
            }
            // Close the streams
            tin.close();//finished  reading existing entries 
            // Compress new files

            for (int i = 0; i < flist.length; i++) {
                if (flist[i].isDirectory()) {
                    continue;
                }
                InputStream fis = new FileInputStream(flist[i]);
                TarArchiveEntry te = new TarArchiveEntry(flist[i],flist[i].getName());
                //te.setSize(flist[i].length());
                tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
                tos.setBigNumberMode(2);
                tos.putArchiveEntry(te); // Add TAR entry to output stream.

                // Transfer bytes from the file to the TAR file
                int count = 0;
                while ((count = fis.read(buf, 0, buffersize)) != -1) {
                    tos.write(buf, 0, count);
                }
                tos.closeArchiveEntry();
                fis.close();
            }
            // Complete the TAR file
            tos.close();
            tempFile.delete();
        }
    }

如果你使用Gradle,请使用以下依赖:
compile group: 'org.apache.commons', name: 'commons-compress', version: '1.+'

我还试过使用org.xeustechnologies:jtar:1.1,但性能远低于org.apache.commons:commons-compress:1.12提供的。

不同实现的性能笔记:

使用Java 1.8 zip压缩10次:
- java.util.zip.ZipEntry;
- java.util.zip.ZipInputStream;
- java.util.zip.ZipOutputStream;

[2016-07-19 19:13:11] 开始
[2016-07-19 19:13:18] 完成
7秒

使用jtar进行10次打包:
- org.xeustechnologies.jtar.TarEntry;
- org.xeustechnologies.jtar.TarInputStream;
- org.xeustechnologies.jtar.TarOutputStream;

[2016-07-19 19:21:23] 开始
[2016-07-19 19:25:18] 完成
3分55秒

调用Cygwin /usr/bin/tar进行10次打包:
[2016-07-19 19:33:04] 开始
[2016-07-19 19:33:14] 完成
14秒

使用org.apache.commons.compress进行100次打包:
- org.apache.commons.compress.archivers.ArchiveEntry;
- org.apache.commons.compress.archivers.tar.TarArchiveEntry;
- org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
- org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;

[2016-07-19 23:04:45] 开始
[2016-07-19 23:04:48] 完成
3秒

使用org.apache.commons.compress进行1000次打包:
[2016-07-19 23:10:28] 开始
[2016-07-19 23:10:48] 完成
20秒


我不知道如何将两个或更多文件夹压缩成一个 .tar 文件。你可以帮我吗? - Guo
基本上,tar文件可以逐个条目地顺序写入。为了回答您的问题,我们需要知道您是否希望在一个tar文件中保留文件夹结构。无论如何,您都可以使用tos.putArchiveEntry(new TarArchiveEntry(name)),其中名称将根据您的要求进行填充。 - aprodan

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