如何在不使用临时文件的情况下从 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 nested
ZipInputStream`。
以下代码演示了这一点。想象一下,我们在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 中的所有模式和表空间?