【问题标题】:How to create a multipart zip file and read it back?如何创建多部分 zip 文件并将其读回?
【发布时间】:2016-12-20 16:29:13
【问题描述】:

如何正确地将字节 zip 压缩到 ByteArrayOutputStream,然后使用 ByteArrayInputStream 读取?我有以下方法:

private byte[] getZippedBytes(final String fileName, final byte[] input) throws Exception {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ZipOutputStream zipOut = new ZipOutputStream(bos);
    ZipEntry entry = new ZipEntry(fileName);
    entry.setSize(input.length);
    zipOut.putNextEntry(entry);
    zipOut.write(input, 0, input.length);
    zipOut.closeEntry();
    zipOut.close();

    //Turn right around and unzip what we just zipped
    ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray()));

    while((entry = zipIn.getNextEntry()) != null) {
        assert entry.getSize() >= 0;
    }

    return bos.toByteArray();
}

当我执行这段代码时,底部的断言失败,因为entry.size-1。我不明白为什么提取的实体与压缩的实体不匹配。

【问题讨论】:

  • 为什么?你已经有了字节。你为什么要压缩和解压缩它们只是为了取回你已经拥有的东西?
  • 这只是一个示例作为概念证明。在我的实际场景中,我正在使用压缩文件的字节创建一个模拟多部分文件,以便我可以测试另一个类是否正确解压缩内容。
  • bos.toByteArray() 的大小是多少?

标签: java zip bytearrayoutputstream zipoutputstream bytearrayinputstream


【解决方案1】:

为什么尺寸是-1?

ZipInputStream 中调用getNextEntry 只需将读取光标定位在要读取的条目的开头。

大小(连同其他元数据)存储在实际数据的末尾,因此当光标位于开头时不容易获得。

这些信息只有在您阅读整个条目数据或只是转到下一个条目后才可用。

例如,转到下一个条目:

// position at the start of the first entry
entry = zipIn.getNextEntry();
ZipEntry firstEntry = entry;    
// size is not yet available
System.out.println("before " + firstEntry.getSize()); // prints -1

// position at the start of the second entry
entry = zipIn.getNextEntry();
// size is now available
System.out.println("after " + firstEntry.getSize()); // prints the size

或读取整个条目数据:

// position at the start of the first entry
entry = zipIn.getNextEntry();
// size is not yet available
System.out.println("before " + entry.getSize()); // prints -1

// read the whole entry data
while(zipIn.read() != -1);

// size is now available
System.out.println("after " + entry.getSize()); // prints the size

您的误解很常见,并且有许多关于此问题的错误报告(已关闭为“不是问题”),例如JDK-4079029JDK-4113731, JDK-6491622.

正如错误报告中提到的,您可以使用ZipFile 而不是ZipInputStream,这将允许在访问条目数据之前获得大小信息;但要创建ZipFile,您需要File(参见构造函数)而不是字节数组。

例如:

File file = new File( "test.zip" );
ZipFile zipFile = new ZipFile(file);

Enumeration enumeration = zipFile.entries();
while (enumeration.hasMoreElements()) {
    ZipEntry zipEntry = (ZipEntry) enumeration.nextElement();
    System.out.println(zipEntry.getSize()); // prints the size
}

如何从输入流中获取数据?

如果您想检查解压后的数据是否与原始输入数据相同,您可以像这样从输入流中读取:

byte[] output = new byte[input.length];
entry = zipIn.getNextEntry();
zipIn.read(output);

System.out.println("Are they equal? " + Arrays.equals(input, output));

// and if we want the size
zipIn.getNextEntry(); // or zipIn.read();
System.out.println("and the size is " + entry.getSize());

现在output 的内容应该与input 相同。

【讨论】:

  • ZipEntry#getSize() 而言,显然使用ZipInputStream#closeEntry()ZipInputStream#getNextEntry() 具有相同的效果。在任何情况下,上述两种方法都不允许在调用前一个条目的数据后读取它们。
  • @RavindraHV 如果你仔细想想,这很合乎逻辑:根据closeEntry() 的 Javadoc:“关闭当前 ZIP 条目并定位流以读取下一个条目。”这实际上对我来说(基于我对 ZIP 布局的有限知识)意味着必须将条目读入黑洞才能“关闭”它。在这种情况下,他们可能利用了getNextEntry()closeEntry() 中的一些公共设施,而该公共设施又设置了上一个条目的size
【解决方案2】:

如何压缩byte[]并解压回来?

我经常使用以下方法对小的byte[] 进行放气/膨胀(压缩/解压缩)(即当它适合内存时)。它基于example given in the Deflater javadoc 并使用Deflater 类压缩数据并使用Inflater 类将其解压缩回来:

public static byte[] compress(byte[] source, int level) {
    Deflater compresser = new Deflater(level);
    compresser.setInput(source);
    compresser.finish();
    byte[] buf = new byte[1024];
    ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
    int n;
    while ((n = compresser.deflate(buf)) > 0)
        bos.write(buf, 0, n);
    compresser.end();
    return bos.toByteArray(); // You could as well return "bos" directly
}

public static byte[] uncompress(byte[] source) {
    Inflater decompresser = new Inflater();
    decompresser.setInput(source);
    byte[] buf = new byte[1024];
    ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
    try {
        int n;
        while ((n = decompresser.inflate(buf)) > 0)
            bos.write(buf, 0, n);
        return bos.toByteArray();
    } catch (DataFormatException e) {
        return null;
    } finally {
        decompresser.end();
    }
}

不需要ByteArrayInputStream,但如果你真的想的话,你可以使用InflaterInputStream 包裹它(但直接使用Inflater 更容易)。

【讨论】:

  • 对于那些想要再次投票而不评论如何改进答案的人(这不是非法的),问题是“如何将字节压缩到ByteArrayOutputStream 并返回”,而不是“如何使用ZipFile"实现压缩。
  • 可能应该编辑标题(稍后我会找到更好的内容),但手头的问题是在解压缩时阅读ZipEntry 详细信息。你的回答没有解决这个问题。
  • @SotiriosDelimanolis 感谢您的反馈。对我来说,标题听起来不错,但ZipEntry 是实现它的错误工具,因此我的回答。但再次感谢让我有机会解释自己:)
  • 基于this SO 更新#4,Deflater(和Inflater)有其自身的问题。
  • @D.Kovács 感谢您的链接。 OpenJDK bug中给出的解决方法是调用Deflater/Inflater.end(),就是上面的代码。
猜你喜欢
  • 1970-01-01
  • 2013-09-06
  • 1970-01-01
  • 1970-01-01
  • 2018-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多