如何从内存中的流式 zip 文件访问 zipEntry
How to access a zipEntry from a streamed zip file in memory
我目前正在实现一个电子阅读器库 (skyepub),它要求我实现一个方法来检查 zipEntry 是否存在。在他们的演示版本中,解决方案很简单:
public boolean isExists(String baseDirectory,String contentPath) {
setupZipFile(baseDirectory,contentPath);
if (this.isCustomFont(contentPath)) {
String path = baseDirectory +"/"+ contentPath;
File file = new File(path);
return file.exists();
}
ZipEntry entry = this.getZipEntry(contentPath);
if (entry==null) return false;
else return true;
}
// Entry name should start without / like META-INF/container.xml
private ZipEntry getZipEntry(String contentPath) {
if (zipFile==null) return null;
String[] subDirs = contentPath.split(Pattern.quote(File.separator));
String corePath = contentPath.replace(subDirs[1], "");
corePath=corePath.replace("//", "");
ZipEntry entry = zipFile.getEntry(corePath.replace(File.separatorChar, '/'));
return entry;
}
如您所见,您可以使用 getZipEntry(contentPath);
在 O(1) 时间内访问有问题的 ZipEntry
但是,在我的例子中,我无法直接从文件系统读取 zip 文件(出于安全原因,它必须从内存中读取)。所以我的 ifExists
实现实际上是通过 zip 文件 一次一个条目,直到找到有问题的zipEntry,这里是相关部分:
try {
final InputStream stream = dbUtil.getBookStream(bookEditionID);
if( stream == null) return null;
final ZipInputStream zip = new ZipInputStream(stream);
ZipEntry entry;
do {
entry = zip.getNextEntry();
if( entry == null) {
zip.close();
return null;
}
} while( !entry.getName().equals(zipEntryName));
} catch( IOException e) {
Log.e("demo", "Can't get content data for "+contentPath);
return null;
}
return data;
所以如果数据存在,ifExists
returns 为真,否则如果为空则为假。
问题
有没有办法在 O(1) 时间而不是 O(n) 时间内从整个 ZipInputStream 中找到有问题的 zip 条目?
相关
参见问题
和 回答。
如果存档的内容在内存中,那么它是可搜索的,您可以搜索中央目录并自己使用。 ZipFile
和 Apache Commons Compress 的同等功能现在都不能用于除 File
之外的任何其他东西,但其他开源库可能(不确定 zip4j)。
Apache Commons Compress'ZipFile
中搜索中央目录并解析它的代码应该很容易适应存档作为 byte[]
可用的情况。事实上,有一个尚未应用的补丁可以作为 COMPRESS-327.
的一部分提供帮助
zip 存档中的条目实际上无法在 O(1) 时间内加载。如果我们看一下 zip archive 的结构,它看起来像这样:
[local file header 1]
[encryption header 1]
[file data 1]
[data descriptor 1]
...
[local file header n]
[encryption header n]
[file data n]
[data descriptor n]
[archive decryption header]
[archive extra data record]
[central directory header 1]
.
[central directory header n]
[zip64 end of central directory record]
[zip64 end of central directory locator]
[end of central directory record]
基本上,压缩文件有一些 headers 加上一个 "central directory",其中包含有关文件的所有元数据(中央目录 headers)。查找条目的唯一有效方法是扫描中央目录 (more info):
...must not scan for entries from the top of the ZIP file, because only the central directory specifies where a file chunk starts
因为中央目录 headers 上没有索引,您只能在 O(n)
中获得一个条目,其中 n
是存档中的文件数。
更新: 不幸的是,我所知道的所有使用流而不是文件的 zip 库都使用本地文件 headers 并扫描包括内容在内的整个流。它们也不容易弯曲。避免扫描我发现的整个档案的唯一方法是自己调整一个库。
更新 2: 为了您的目的,我冒昧地修改了上述 zip4j 库。假设您在字节数组中读取了 zip 文件,并且添加了对 zip4j 版本 1.3.2 的依赖,您可以像这样使用 MemoryHeaderReader and RandomByteStream:
String myZipFile = "...";
byte[] bytes = readFile();
MemoryHeaderReader headerReader = new MemoryHeaderReader(RandomAccessStream.fromBytes(bytes));
ZipModel zipModel = headerReader.readAllHeaders();
FileHeader myFile = Zip4jUtil.getFileHeader(zipModel, myZipFile)
boolean fileIsPresent = myFile != null;
它在 O(entryCount) 中工作,无需读取整个档案,这应该相当快。我还没有对它进行彻底的测试,但它应该会让您了解如何根据您的目的调整 zip4j。
从技术上讲,搜索总是 O(n),其中 n 是 zip 文件中的条目数,因为您有通过中央目录或通过本地 headers.
进行线性搜索
您似乎暗示 zip 文件已完全加载到内存中。在这种情况下,最快的做法是在中央目录中搜索条目。如果找到它,该目录条目将指向本地 header.
如果您对同一个 zip 文件进行大量搜索,那么您可以在 O(n) 时间,然后使用它在大约 O(1) 时间内搜索给定名称。
我目前正在实现一个电子阅读器库 (skyepub),它要求我实现一个方法来检查 zipEntry 是否存在。在他们的演示版本中,解决方案很简单:
public boolean isExists(String baseDirectory,String contentPath) {
setupZipFile(baseDirectory,contentPath);
if (this.isCustomFont(contentPath)) {
String path = baseDirectory +"/"+ contentPath;
File file = new File(path);
return file.exists();
}
ZipEntry entry = this.getZipEntry(contentPath);
if (entry==null) return false;
else return true;
}
// Entry name should start without / like META-INF/container.xml
private ZipEntry getZipEntry(String contentPath) {
if (zipFile==null) return null;
String[] subDirs = contentPath.split(Pattern.quote(File.separator));
String corePath = contentPath.replace(subDirs[1], "");
corePath=corePath.replace("//", "");
ZipEntry entry = zipFile.getEntry(corePath.replace(File.separatorChar, '/'));
return entry;
}
如您所见,您可以使用 getZipEntry(contentPath);
但是,在我的例子中,我无法直接从文件系统读取 zip 文件(出于安全原因,它必须从内存中读取)。所以我的 ifExists
实现实际上是通过 zip 文件 一次一个条目,直到找到有问题的zipEntry,这里是相关部分:
try {
final InputStream stream = dbUtil.getBookStream(bookEditionID);
if( stream == null) return null;
final ZipInputStream zip = new ZipInputStream(stream);
ZipEntry entry;
do {
entry = zip.getNextEntry();
if( entry == null) {
zip.close();
return null;
}
} while( !entry.getName().equals(zipEntryName));
} catch( IOException e) {
Log.e("demo", "Can't get content data for "+contentPath);
return null;
}
return data;
所以如果数据存在,ifExists
returns 为真,否则如果为空则为假。
问题
有没有办法在 O(1) 时间而不是 O(n) 时间内从整个 ZipInputStream 中找到有问题的 zip 条目?
相关
参见
如果存档的内容在内存中,那么它是可搜索的,您可以搜索中央目录并自己使用。 ZipFile
和 Apache Commons Compress 的同等功能现在都不能用于除 File
之外的任何其他东西,但其他开源库可能(不确定 zip4j)。
Apache Commons Compress'ZipFile
中搜索中央目录并解析它的代码应该很容易适应存档作为 byte[]
可用的情况。事实上,有一个尚未应用的补丁可以作为 COMPRESS-327.
zip 存档中的条目实际上无法在 O(1) 时间内加载。如果我们看一下 zip archive 的结构,它看起来像这样:
[local file header 1]
[encryption header 1]
[file data 1]
[data descriptor 1]
...
[local file header n]
[encryption header n]
[file data n]
[data descriptor n]
[archive decryption header]
[archive extra data record]
[central directory header 1]
.
[central directory header n]
[zip64 end of central directory record]
[zip64 end of central directory locator]
[end of central directory record]
基本上,压缩文件有一些 headers 加上一个 "central directory",其中包含有关文件的所有元数据(中央目录 headers)。查找条目的唯一有效方法是扫描中央目录 (more info):
...must not scan for entries from the top of the ZIP file, because only the central directory specifies where a file chunk starts
因为中央目录 headers 上没有索引,您只能在 O(n)
中获得一个条目,其中 n
是存档中的文件数。
更新: 不幸的是,我所知道的所有使用流而不是文件的 zip 库都使用本地文件 headers 并扫描包括内容在内的整个流。它们也不容易弯曲。避免扫描我发现的整个档案的唯一方法是自己调整一个库。
更新 2: 为了您的目的,我冒昧地修改了上述 zip4j 库。假设您在字节数组中读取了 zip 文件,并且添加了对 zip4j 版本 1.3.2 的依赖,您可以像这样使用 MemoryHeaderReader and RandomByteStream:
String myZipFile = "...";
byte[] bytes = readFile();
MemoryHeaderReader headerReader = new MemoryHeaderReader(RandomAccessStream.fromBytes(bytes));
ZipModel zipModel = headerReader.readAllHeaders();
FileHeader myFile = Zip4jUtil.getFileHeader(zipModel, myZipFile)
boolean fileIsPresent = myFile != null;
它在 O(entryCount) 中工作,无需读取整个档案,这应该相当快。我还没有对它进行彻底的测试,但它应该会让您了解如何根据您的目的调整 zip4j。
从技术上讲,搜索总是 O(n),其中 n 是 zip 文件中的条目数,因为您有通过中央目录或通过本地 headers.
进行线性搜索您似乎暗示 zip 文件已完全加载到内存中。在这种情况下,最快的做法是在中央目录中搜索条目。如果找到它,该目录条目将指向本地 header.
如果您对同一个 zip 文件进行大量搜索,那么您可以在 O(n) 时间,然后使用它在大约 O(1) 时间内搜索给定名称。