从 ZipInputStream 读取到 ByteArrayOutputStream 中

Reading from a ZipInputStream into a ByteArrayOutputStream

提问人:pkaeding 提问时间:9/16/2008 最后编辑:jocepkaeding 更新时间:10/26/2017 访问量:53510

问:

我正在尝试从 中读取单个文件并将其复制到 (这样我就可以创建一个并将其交给最终将关闭流的第 3 方库,并且我不希望我的被关闭)。java.util.zip.ZipInputStreamjava.io.ByteArrayOutputStreamjava.io.ByteArrayInputStreamZipInputStream

我可能在这里遗漏了一些基本的东西,但我从不在这里进入 while 循环:

ByteArrayOutputStream streamBuilder = new ByteArrayOutputStream();
int bytesRead;
byte[] tempBuffer = new byte[8192*2];
try {
    while ((bytesRead = zipStream.read(tempBuffer)) != -1) {
        streamBuilder.write(tempBuffer, 0, bytesRead);
    }
} catch (IOException e) {
    // ...
}

我缺少什么可以复制流?

编辑:

我应该之前提到这不是来自文件,所以我认为我不能使用 .它来自通过 servlet 上传的文件。ZipInputStreamZipFile

另外,在进入这段代码之前,我已经调用了。如果我不尝试将文件复制到另一个文件(通过上面提到的),而只是将其传递给我的第三方库,则该库将关闭流,并且我无法执行任何其他操作,例如处理流中的剩余文件。getNextEntry()ZipInputStreamInputStreamOutputStreamZipInputStream

java zipinputstream zipoutputstream

评论

1赞 helios 12/31/2009
也许你现在不在乎,但如果你包装原始输入流 (zipStream) 并重写 close 方法,你可以避免复制所有数据并避免 3d party 库关闭流。1) 创建一个公共类 DontCloseInputStream extends FilterInputStream。2) 创建一个调用 super(in) 的构造函数 (InputStream in) 3) 重写 close 方法,什么都不做 4) 创建新的 DontCloseInputStream(zipStream) 5) 将其传递给库。And voi lá
0赞 helios 12/31/2009
为了将 InputStream 复制到 OutputStream 上,commons-fileupload 库 (Apache) 中有一个名为 Streams 的实用程序类。你执行 Streams.copy(in, out, close?) 就完成了。
0赞 Boris Bokowski 9/16/2008
那么zipEntry.getSize()返回什么呢?
0赞 pkaeding 9/17/2008
在本例中,zipEntry.getSize() 返回一个合理的数字 28689。

答:

-1赞 Sunny Milenov 9/16/2008 #1

检查输入流是否位于乞求中。

否则,作为实现:我认为您不需要在阅读时写入结果流,除非您在另一个线程中处理此确切的流。

只需创建一个字节数组,读取输入流,然后创建输出流。

0赞 Boris Bokowski 9/16/2008 #2

目前尚不清楚您是如何获得 zipStream 的。当你得到它时,它应该可以工作:

  zipStream = zipFile.getInputStream(zipEntry)

评论

0赞 pkaeding 9/16/2008
我刚刚对此进行了澄清,但它不是从文件中来的。
4赞 ScArcher2 9/16/2008 #3

我会使用 commons io 项目中的 IOUtils

IOUtils.copy(zipStream, byteArrayOutputStream);

评论

0赞 pkaeding 9/16/2008
这看起来可能会起作用。明天上班时我会尝试一下。谢谢。
7赞 Benedikt Waldvogel 9/16/2008 #4

您可能尝试过从这样的阅读中阅读:FileInputStream

ZipInputStream in = new ZipInputStream(new FileInputStream(...));

这是行不通的,因为 zip 存档可以包含多个文件,并且您需要指定要读取的文件。

您可以使用java.util.zip.ZipFile和库(例如Apache Commons IO的IOUtilsGuava的ByteStreams)来帮助您复制流。

例:

ByteArrayOutputStream out = new ByteArrayOutputStream();
try (ZipFile zipFile = new ZipFile("foo.zip")) {
    ZipEntry zipEntry = zipFile.getEntry("fileInTheZip.txt");

    try (InputStream in = zipFile.getInputStream(zipEntry)) {
        IOUtils.copy(in, out);
    }
}
0赞 helios 9/16/2008 #5

目前尚不清楚您是如何获得 zipStream 的。当你得到它时,它应该可以工作:

  zipStream = zipFile.getInputStream(zipEntry)

如果要从 ZipFile 获取 ZipInputStream,则可以获取 3d 参与方库的一个流,让它使用它,然后使用前面的代码获取另一个输入流。

请记住,输入流是一个游标。如果你有整个数据(如 ZipFile),你可以要求 N 个游标覆盖它。

另一种情况是,如果您只有一个“GZip”输入流,只有一个压缩字节流。在这种情况下,ByteArrayOutputStream 缓冲区就很有意义了。

1赞 Boris Bokowski 9/16/2008 #6

我会在 ZipInputStream 上调用 getNextEntry(),直到它位于您想要的条目处(使用 ZipEntry.getName() 等)。调用 getNextEntry() 会将“光标”前进到它返回的条目的开头。然后,使用 ZipEntry.getSize() 确定应该使用 zipInputStream.read() 读取多少字节。

评论

0赞 pkaeding 9/16/2008
在进入这个片段之前,我实际上已经调用了getNextEntry()。我只是对这个问题进行了一些澄清。
3赞 jt. 9/16/2008 #7

您可以围绕忽略 close() 的 ZipInputStream 实现自己的包装器,并将其移交给第三方库。

thirdPartyLib.handleZipData(new CloseIgnoringInputStream(zipStream));


class CloseIgnoringInputStream extends InputStream
{
    private ZipInputStream stream;

    public CloseIgnoringInputStream(ZipInputStream inStream)
    {
        stream = inStream;
    }

    public int read() throws IOException {
        return stream.read();
    }

    public void close()
    {
        //ignore
    }

    public void reallyClose() throws IOException
    {
        stream.close();
    }
}
9赞 Kevin Day 9/16/2008 #8

您的循环看起来有效 - 以下代码(仅靠它自己)返回什么?

zipStream.read(tempBuffer)

如果它返回 -1,则 zipStream 在你得到它之前就关闭了,并且所有赌注都关闭了。是时候使用调试器并确保传递给您的内容实际上有效了。

当您调用 getNextEntry() 时,它是否返回一个值,以及条目中的数据是否有意义(即 getCompressedSize() 是否返回有效值)?如果您只是在读取未嵌入预读 zip 条目的 Zip 文件,则 ZipInputStream 不适合您。

关于 Zip 格式的一些有用花絮:

嵌入在 zip 文件中的每个文件都有一个标题。此标头可以包含有用的信息(例如流的压缩长度、文件中的偏移量、CRC) - 或者它可以包含一些神奇的值,这些值基本上说“信息不在流标头中,您必须检查 Zip post-amble”。

然后,每个 zip 文件都有一个表,该表附加到文件末尾,其中包含所有 zip 条目以及实际数据。末尾的表是必填的,其中的值必须正确。相反,不必提供流中嵌入的值。

如果使用 ZipFile,它将读取 zip 末尾的表。如果您使用 ZipInputStream,我怀疑 getNextEntry() 会尝试使用流中嵌入的条目。如果未指定这些值,则 ZipInputStream 不知道流可能有多长。inflate 算法是自终止的(您实际上不需要知道输出流的未压缩长度即可完全恢复输出),但此读取器的 Java 版本可能无法很好地处理这种情况。

我要说的是,让 servlet 返回 ZipInputStream 是相当不寻常的(如果您要接收压缩内容,接收 inflatorInputStream 更为常见。

评论

0赞 Ethan Heilman 7/18/2009
java 中的 ZipInputStream 不能很好地处理这个问题。感谢您发布此内容。
0赞 Dmytro 1/19/2010 #9

请尝试以下代码

private static byte[] getZipArchiveContent(File zipName) throws WorkflowServiceBusinessException {

  BufferedInputStream buffer = null;
  FileInputStream fileStream = null;
  ByteArrayOutputStream byteOut = null;
  byte data[] = new byte[BUFFER];

  try {
   try {
    fileStream = new FileInputStream(zipName);
    buffer = new BufferedInputStream(fileStream);
    byteOut = new ByteArrayOutputStream();

    int count;
    while((count = buffer.read(data, 0, BUFFER)) != -1) {
     byteOut.write(data, 0, count);
    }
   } catch(Exception e) {
    throw new WorkflowServiceBusinessException(e.getMessage(), e);
   } finally {
    if(null != fileStream) {
     fileStream.close();
    }
    if(null != buffer) {
     buffer.close();
    }
    if(null != byteOut) {
     byteOut.close();
    }
   }
  } catch(Exception e) {
   throw new WorkflowServiceBusinessException(e.getMessage(), e);
  }
  return byteOut.toByteArray();

 }
4赞 Juan Ignacio 4/4/2012 #10

您错过了来电

ZipEntry 条目 = (ZipEntry) zipStream.getNextEntry();

定位第一个条目解压缩的第一个字节。

 ByteArrayOutputStream streamBuilder = new ByteArrayOutputStream();
 int bytesRead;
 byte[] tempBuffer = new byte[8192*2];
 ZipEntry entry = (ZipEntry) zipStream.getNextEntry();
 try {
     while ( (bytesRead = zipStream.read(tempBuffer)) != -1 ){
        streamBuilder.write(tempBuffer, 0, bytesRead);
     }
 } catch (IOException e) {
      ...
 }