Java ZipFileSystem 在遍历时不保留物理顺序
Java ZipFileSystem does not retain physical order while traversing
让我们考虑一个非常简单的 Java 片段:
String pathUriStr = Paths.get(args[0]).toUri().toASCIIString();
URI zipUri = URI.create("jar:" + pathUriStr);
FileSystem zip = null;
try {
zip = FileSystems.newFileSystem(zipUri, Collections.emptyMap());
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Path zipRoot = zip.getPath("/");
System.out.println("ZipFileSystem:");
FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
System.out.println(file);
return super.visitFile(file, attrs);
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
System.out.println(dir);
return super.preVisitDirectory(dir, attrs);
}
};
try {
Files.walkFileTree(zipRoot, visitor);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
输出为:
ZipFileSystem:
/
/images
/images/ant_logo_large.gif
/org
/org/apache
...
/META-INF
/META-INF/LICENSE.txt
/META-INF/MANIFEST.MF
对 `ZipInputStream 做同样的事情:
System.out.println("ZipInputStream:");
try (InputStream is = Files.newInputStream(Paths.get(args[0]), StandardOpenOption.READ);
ZipInputStream zipIs = new ZipInputStream(is)) {
ZipEntry entry = null;
while((entry = zipIs.getNextEntry()) != null) {
System.out.println(entry);
zipIs.closeEntry();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
给我:
ZipInputStream:
META-INF/
META-INF/MANIFEST.MF
org/
org/apache/
...
META-INF/LICENSE.txt
images/
images/ant_logo_large.gif
与 unzip(1)
:
的输出相同
$ unzip -l ~/ant-1.5.jar
Archive: /net/home/osipovmi/ant-1.5.jar
Length Date Time Name
--------- ---------- ----- ----
0 07-09-2002 11:13 META-INF/
460 07-09-2002 11:13 META-INF/MANIFEST.MF
0 07-09-2002 11:12 org/
0 07-09-2002 11:12 org/apache/
.......................................................
2766 07-09-2002 11:10 META-INF/LICENSE.txt
0 04-30-2002 10:10 images/
5360 03-18-2002 14:57 images/ant_logo_large.gif
--------- -------
1329851 435 files
虽然这首先看起来不像是一个问题,但这是一个巨大的问题,因为这个 JAR 将被拒绝,因为 META-INF/
和 META-INF/MANIFEST.MF
不是根据 JAR 规范的第一个条目.
我的用例非常相似,我想使用 ZIP 文件并要求某些条目成为第一个条目以快速验证输入而无需查找文件末尾。
全部使用 Java 8、10 和 11-ea 进行了测试。
那么问题来了,为什么 Zip 文件系统不保留流中出现的顺序?
ZipFileSystem
是底层 zip 文件的抽象,允许您以与 FileSystem 的任何其他实现相同的方式对待它。
从根 /
开始,让它在您期望的常规树状结构中工作是非常有意义的。如果您想轻松复制文件 from/to zip,这非常好,就像您在常规目录之间所做的那样。这样做的缺点是您失去了对 zip 本身的一些控制。
ZipInputStream
是一个低得多的抽象层次,您可以在其中控制结构和其他 zip 文件特定的东西。缺点是您可能需要编写更多代码。
示例:您想将文件从一个 zip 复制或移动到另一个:
使用 ZipFileSystem
这相当于将文件从一个目录移动到另一个目录,代码适用于 FileSystem
的任何实现。使用 zip 流,您必须手动处理源 zip 文件,找到正确的条目,删除条目,然后将其添加到第二个 zip 文件,同时重写文件。那是 1 行代码与几行、循环和其他样板代码的对比。
因此 ZipFileSystem
为您提供更高抽象的好处,但也有内存使用、对 zip 文件细节的控制较少等缺点。 ZipXXXStream
为您提供 zip 的低级视图,处理 zip 条目和其他您并不总是需要的内部细节。
让我们考虑一个非常简单的 Java 片段:
String pathUriStr = Paths.get(args[0]).toUri().toASCIIString();
URI zipUri = URI.create("jar:" + pathUriStr);
FileSystem zip = null;
try {
zip = FileSystems.newFileSystem(zipUri, Collections.emptyMap());
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Path zipRoot = zip.getPath("/");
System.out.println("ZipFileSystem:");
FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
System.out.println(file);
return super.visitFile(file, attrs);
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
System.out.println(dir);
return super.preVisitDirectory(dir, attrs);
}
};
try {
Files.walkFileTree(zipRoot, visitor);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
输出为:
ZipFileSystem:
/
/images
/images/ant_logo_large.gif
/org
/org/apache
...
/META-INF
/META-INF/LICENSE.txt
/META-INF/MANIFEST.MF
对 `ZipInputStream 做同样的事情:
System.out.println("ZipInputStream:");
try (InputStream is = Files.newInputStream(Paths.get(args[0]), StandardOpenOption.READ);
ZipInputStream zipIs = new ZipInputStream(is)) {
ZipEntry entry = null;
while((entry = zipIs.getNextEntry()) != null) {
System.out.println(entry);
zipIs.closeEntry();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
给我:
ZipInputStream:
META-INF/
META-INF/MANIFEST.MF
org/
org/apache/
...
META-INF/LICENSE.txt
images/
images/ant_logo_large.gif
与 unzip(1)
:
$ unzip -l ~/ant-1.5.jar
Archive: /net/home/osipovmi/ant-1.5.jar
Length Date Time Name
--------- ---------- ----- ----
0 07-09-2002 11:13 META-INF/
460 07-09-2002 11:13 META-INF/MANIFEST.MF
0 07-09-2002 11:12 org/
0 07-09-2002 11:12 org/apache/
.......................................................
2766 07-09-2002 11:10 META-INF/LICENSE.txt
0 04-30-2002 10:10 images/
5360 03-18-2002 14:57 images/ant_logo_large.gif
--------- -------
1329851 435 files
虽然这首先看起来不像是一个问题,但这是一个巨大的问题,因为这个 JAR 将被拒绝,因为 META-INF/
和 META-INF/MANIFEST.MF
不是根据 JAR 规范的第一个条目.
我的用例非常相似,我想使用 ZIP 文件并要求某些条目成为第一个条目以快速验证输入而无需查找文件末尾。
全部使用 Java 8、10 和 11-ea 进行了测试。
那么问题来了,为什么 Zip 文件系统不保留流中出现的顺序?
ZipFileSystem
是底层 zip 文件的抽象,允许您以与 FileSystem 的任何其他实现相同的方式对待它。
从根 /
开始,让它在您期望的常规树状结构中工作是非常有意义的。如果您想轻松复制文件 from/to zip,这非常好,就像您在常规目录之间所做的那样。这样做的缺点是您失去了对 zip 本身的一些控制。
ZipInputStream
是一个低得多的抽象层次,您可以在其中控制结构和其他 zip 文件特定的东西。缺点是您可能需要编写更多代码。
示例:您想将文件从一个 zip 复制或移动到另一个:
使用 ZipFileSystem
这相当于将文件从一个目录移动到另一个目录,代码适用于 FileSystem
的任何实现。使用 zip 流,您必须手动处理源 zip 文件,找到正确的条目,删除条目,然后将其添加到第二个 zip 文件,同时重写文件。那是 1 行代码与几行、循环和其他样板代码的对比。
因此 ZipFileSystem
为您提供更高抽象的好处,但也有内存使用、对 zip 文件细节的控制较少等缺点。 ZipXXXStream
为您提供 zip 的低级视图,处理 zip 条目和其他您并不总是需要的内部细节。