如何在不使用临时文件的情况下从 Java 中的嵌套 zip 文件中读取数据?

Posted

技术标签:

【中文标题】如何在不使用临时文件的情况下从 Java 中的嵌套 zip 文件中读取数据?【英文标题】:How to read data from nested zip files in Java without using temporary files? 【发布时间】:2018-04-22 20:46:01 【问题描述】:

我正在尝试从嵌套的 zip 存档中提取文件并在内存中处理它们。

这个问题不是关于什么:

    如何在 Java 中读取 zip 文件:不,问题是如何在 zip 中读取 zip 文件中的 zip 文件,依此类推(如嵌套 zip 文件中)。

    在磁盘上写入临时结果:不,我要在内存中完成所有操作。我使用将结果临时写入磁盘的效率不高的技术找到了许多答案,但这不是我想要做的。

例子:

Zipfile -> Zipfile1 -> Zipfile2 -> Zipfile3

目标:提取每个嵌套 zip 文件中的数据,全部在内存中并使用 Java。

ZipFile 是答案,你说?不,不是,它适用于第一次迭代,即:

Zipfile -> Zipfile1

但是一旦你到达 Zipfile2,并执行:

ZipInputStream z = new ZipInputStream(zipFile.getInputStream( zipEntry) ) ;

你会得到一个 NullPointerException。

我的代码:

public class ZipHandler 

    String findings = new String();
    ZipFile zipFile = null;

    public void init(String fileName) throws AppException

        try 
        //read file into stream
        zipFile = new ZipFile(fileName);  
        Enumeration<?> enu = zipFile.entries();  
        exctractInfoFromZip(enu);

        zipFile.close();
         catch (FileNotFoundException e) 
        e.printStackTrace();

         catch (IOException e) 
            e.printStackTrace();
    


//The idea was recursively extract entries using ZipFile
public void exctractInfoFromZip(Enumeration<?> enu) throws IOException, AppException   

    try 
        while (enu.hasMoreElements())  
            ZipEntry zipEntry = (ZipEntry) enu.nextElement();

            String name = zipEntry.getName();
            long size = zipEntry.getSize();
            long compressedSize = zipEntry.getCompressedSize();

            System.out.printf("name: %-20s | size: %6d | compressed size: %6d\n", 
                    name, size, compressedSize);

            // directory ?
            if (zipEntry.isDirectory()) 
                System.out.println("dir found:" + name);
                findings+=", " + name; 
                continue;
             

            if (name.toUpperCase().endsWith(".ZIP") ||  name.toUpperCase().endsWith(".GZ")) 
                String fileType = name.substring(
                        name.lastIndexOf(".")+1, name.length());

                System.out.println("File type:" + fileType);
                System.out.println("zipEntry: " + zipEntry);

                if (fileType.equalsIgnoreCase("ZIP")) 
//ZipFile here returns a NULL pointer when you try to get the first nested zip
                    ZipInputStream z = new ZipInputStream(zipFile.getInputStream(zipEntry) ) ;
                    System.out.println("Opening ZIP as stream: " + name);

                    findings+=", " + name;

                    exctractInfoFromZip(zipInputStreamToEnum(z));
                 else if (fileType.equalsIgnoreCase("GZ")) 
//ZipFile here returns a NULL pointer when you try to get the first nested zip      
                    GZIPInputStream z = new GZIPInputStream(zipFile.getInputStream(zipEntry) ) ;
                    System.out.println("Opening ZIP as stream: " + name);

                    findings+=", " + name;

                    exctractInfoFromZip(gZipInputStreamToEnum(z));
                 else
                    throw new AppException("extension not recognized!");
             else 
                System.out.println(name);
                findings+=", " + name;
            
        
     catch (IOException e) 
        // TODO Auto-generated catch block
        e.printStackTrace();
    

    System.out.println("Findings " + findings);
 

public Enumeration<?> zipInputStreamToEnum(ZipInputStream zStream) throws IOException

    List<ZipEntry> list = new ArrayList<ZipEntry>();    

    while (zStream.available() != 0) 
        list.add(zStream.getNextEntry());
    

    return Collections.enumeration(list);
 

【问题讨论】:

"(将很快编辑)" - 不要发布部分问题。等到您提出完整的问题后再发布。 您的主要问题是您首先必须在 ZipInputStream 上使用 getNextEntry() 寻找正确的 zip 条目。 抱歉发布错误。下班后会更正帖子。我真的被这个困住了 以后请考虑把你的代码变成minimal reproducible example;也就是说,最好是一个带有 main() 方法的 .java 文件,我们可以编译并运行它来重现问题。 无论如何,你真正的问题似乎是你的 zipFile 变量总是引用最外面的文件。尝试将 ZipEntry 从内部 ZipInputStreams 之一传递到 zipFile.getInputStream() 会失败,这并不奇怪。 (唯一的惊喜是它不会抛出 IllegalArgumentException。)由于似乎没有任何方法可以从 InputStream 实例化 ZipFile,因此您最好的选择似乎是放弃 ZipFile 并直接使用 ZipInputStreams,例如JMax 建议如下。 【参考方案1】:

我没有尝试过,但是使用ZipInputStream 你可以读取任何InputStream that contains a ZIP file as data. Iterate through the entries and when you found the correct entry use the ZipInputStreamto create another nestedZipInputStream`。

以下代码演示了这一点。想象一下,我们在0.zip 中有一个readme.txt,它又被压缩在1.zip 中,它被压缩在2.zip 中。现在我们从readme.txt阅读一些文字:

try (FileInputStream fin = new FileInputStream("D:/2.zip")) 
    ZipInputStream firstZip = new ZipInputStream(fin);
    ZipInputStream zippedZip = new ZipInputStream(findEntry(firstZip, "1.zip"));
    ZipInputStream zippedZippedZip = new ZipInputStream(findEntry(zippedZip, "0.zip"));

    ZipInputStream zippedZippedZippedReadme = findEntry(zippedZippedZip, "readme.txt");
    InputStreamReader reader = new InputStreamReader(zippedZippedZippedReadme);
    char[] cbuf = new char[1024];
    int read = reader.read(cbuf);
    System.out.println(new String(cbuf, 0, read));
    .....

public static ZipInputStream findEntry(ZipInputStream in, String name) throws IOException 
    ZipEntry entry = null;
    while ((entry = in.getNextEntry()) != null) 
        if (entry.getName().equals(name)) 
            return in;
        
    
    return null;

请注意,代码真的很丑陋,它不会关闭任何东西,也不会检查错误。它只是一个演示其工作原理的最小化版本。

理论上,您级联到另一个 ZipInputStream 的数量没有限制。数据永远不会写入临时文件。仅当您阅读每个InputStream 时,才按需执行解密。

【讨论】:

Jmax,我不知道原始 input.zip 文件中文件的名称 带有嵌套 zip 的输入 zip 文件包含文件、目录结构和 zip 文件。例如。 input.zip 根文件夹 --file.txt --inputNested.zip --嵌套文件夹 ----file2.wht ----inputNested2.zip 等等 然后枚举条目,检查条目名称/文件名,然后用它做任何你想做的事情。 嗨,我知道这个答案很老了@j【参考方案2】:

这是我发现在内存中解压文件的方式:

代码一点都不干净,但我知道规则是发布一些有效的东西,所以我希望能有所帮助

我所做的是使用递归方法来导航复杂的 ZIP 文件并提取 文件夹 其他内拉链 文件 并将结果保存在内存中以供以后使用。

我发现我想与你分享的主要内容:

1 如果您有嵌套的 zip 文件,则 ZipFile 将毫无用处 2 你必须使用基本的 Zip InputStream 和 Outputstream 3 我只使用递归编程来解压嵌套的 zips

package course.hernan;

import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.IOUtils;

public class FileReader 

private static final int  BUFFER_SIZE = 2048;

    public static void main(String[] args) 
        try 
            File f = new File("DIR/inputs.zip");
            FileInputStream fis = new FileInputStream(f);
            BufferedInputStream bis = new BufferedInputStream(fis);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(baos);
            byte[] buffer = new byte[BUFFER_SIZE];
            while (bis.read(buffer, 0, BUFFER_SIZE) != -1) 
               bos.write(buffer);
            

            bos.flush();
            bos.close();
            bis.close();

            //This STACK has the output byte array information 
            Deque<Map<Integer, Object[]>> outputDataStack = ZipHandler1.unzip(baos);


         catch (FileNotFoundException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
         catch (IOException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
    
    
package course.hernan;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.lang3.StringUtils;

public class ZipHandler1 

  private static final int BUFFER_SIZE = 2048;

  private static final String ZIP_EXTENSION = ".zip";
  public static final Integer FOLDER = 1;
  public static final Integer ZIP = 2;
  public static final Integer FILE = 3;


  public static Deque<Map<Integer, Object[]>> unzip(ByteArrayOutputStream zippedOutputFile) 

    try 

      ZipInputStream inputStream = new ZipInputStream(
          new BufferedInputStream(new ByteArrayInputStream(
              zippedOutputFile.toByteArray())));

      ZipEntry entry;

      Deque<Map<Integer, Object[]>> result = new ArrayDeque<Map<Integer, Object[]>>();

      while ((entry = inputStream.getNextEntry()) != null) 

        LinkedHashMap<Integer, Object[]> map = new LinkedHashMap<Integer, Object[]>();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        System.out.println("\tExtracting entry: " + entry);
        int count;
        byte[] data = new byte[BUFFER_SIZE];

        if (!entry.isDirectory()) 
          BufferedOutputStream out = new BufferedOutputStream(
              outputStream, BUFFER_SIZE);

          while ((count = inputStream.read(data, 0, BUFFER_SIZE)) != -1) 
            out.write(data, 0, count);
          

          out.flush();
          out.close();

          //  recursively unzip files
          if (entry.getName().toUpperCase().endsWith(ZIP_EXTENSION.toUpperCase())) 
            map.put(ZIP, new Object[] entry.getName(), unzip(outputStream));
            result.add(map);
            //result.addAll();
           else  
            map.put(FILE, new Object[] entry.getName(), outputStream);
            result.add(map);
          
         else 
          map.put(FOLDER, new Object[] entry.getName(), unzip(outputStream));
          result.add(map);
        
      

      inputStream.close();

      return result;

     catch (Exception e) 
      throw new RuntimeException(e);
    
  

【讨论】:

以上是关于如何在不使用临时文件的情况下从 Java 中的嵌套 zip 文件中读取数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用 JCL 中的 XSUM 的情况下从输入文件中删除重复项并将重复项写入文件? [关闭]

如何在不使用 ImageView 的情况下从 API 端点下载图像或位图?

如何在不使用 Sybase/Perl 中的游标的情况下从表/文件中的 2 行中获取结果集中的一行

如何在不导入的情况下从 .dmp 文件中列出 Oracle 中的所有模式和表空间?

如何在不使用 sudo 的情况下从终端窗口关闭 Internet 连接?

如何在不复制的情况下从 N 维容器中获取可迭代范围?