关于Java:从ZipInputStream读取到ByteArrayOutputStream

关于Java:从ZipInputStream读取到ByteArrayOutputStream

Reading from a ZipInputStream into a ByteArrayOutputStream

我正在尝试从java.util.zip.ZipInputStream读取单个文件,并将其复制到java.io.ByteArrayOutputStream(这样我便可以创建java.io.ByteArrayInputStream并将其交给第三方库,该库最终将关闭流,并且 我不希望我的ZipInputStream关闭)。

我可能在这里错过了一些基本的东西,但是我从来没有在这里进入while循环:

1
2
3
4
5
6
7
8
9
10
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) {
    // ...
}

我缺少什么让我可以复制信息流?

编辑:

我应该早先提到过,这个ZipInputStream不是来自文件,所以我认为我不能使用ZipFile。 它来自通过Servlet上传的文件。

另外,在进入此代码段之前,我已经在ZipInputStream上调用了getNextEntry()。 如果我不尝试将文件复制到另一个InputStream(通过上述的OutputStream),而只是将ZipInputStream传递给我的第3方库,则该库将关闭流,而我无法执行任何操作 更多,例如处理流中剩余的文件。


您的循环看起来有效-以下代码(仅凭其返回)返回什么?

1
zipStream.read(tempBuffer)

如果返回-1,则在获取zipStream之前将其关闭,并且所有投注均已关闭。现在是时候使用调试器,并确保传递给您的内容实际上是有效的。

当您调用getNextEntry()时,它是否返回值,并且条目中的数据是否有意义(即getCompressedSize()是否返回有效值)?如果您只是在读取未嵌入预读zip条目的Zip文件,则ZipInputStream将无法为您工作。

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

嵌入zip文件中的每个文件都有一个标头。该标头可以包含有用的信息(例如流的压缩长度,它在文件中的偏移量,CRC)-或可以包含一些魔术值,这些魔术值基本上说"信息不在流标头中,您必须检查邮编后序"。

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

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

我要说的是,有一个servlet返回ZipInputStream是非常不寻常的(如果您要接收压缩的内容,则接收inflatorInputStream更为常见。


您可能试图像这样从FileInputStream读取:

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

由于zip归档文件可能包含多个文件,因此您将无法使用此文件,并且您需要指定要读取的文件。

您可以使用java.util.zip.ZipFile和诸如Apache Commons IO的IOUtils或Guava的ByteStreams之类的库来帮助您复制流。

例:

1
2
3
4
5
6
7
8
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);
    }
}

你错过了电话

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

放置第一个条目解压缩的第一个字节。

1
2
3
4
5
6
7
8
9
10
11
 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) {
      ...
 }

我会使用commons io项目中的IOUtils。

1
IOUtils.copy(zipStream, byteArrayOutputStream);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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();
    }
}

我会在ZipInputStream上调用getNextEntry(),直到它位于所需的条目为止(使用ZipEntry.getName()等)。调用getNextEntry()会将"光标"前进到它返回的条目的开头。然后,使用ZipEntry.getSize()确定应使用zipInputStream.read()读取多少个字节。


请尝试以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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();

 }

尚不清楚您如何获得zipStream。如下所示,它应该可以工作:

1
  zipStream = zipFile.getInputStream(zipEntry)

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

记住,输入流是游标。如果您拥有全部数据(如ZipFile),则可以在其上请求N个游标。

不同的情况是,如果只有" GZip"输入流,则只有压缩字节流。在这种情况下,您的ByteArrayOutputStream缓冲区很有意义。


目前尚不清楚您如何获得zipStream。如下所示,它应该可以工作:

1
  zipStream = zipFile.getInputStream(zipEntry)

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

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

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


推荐阅读