Java 7 zip文件系统提供程序似乎不接受URI中的空格

我一直在测试所有可能的变体和排列,但我似乎无法使用zip / jar方案构造一个包含空格的路径(URI)的FileSystemProvider。 Oracle Docs提供了一个非常简单的测试用例。 我冒昧地修改了这个例子,只是在URI中添加空格,它就停止工作了。 下面的代码段:

import java.util.*; import java.net.URI; import java.nio.file.*; public class Test { public static void main(String [] args) throws Throwable { Map env = new HashMap(); env.put("create", "true"); URI uri = new URI("jar:file:/c:/dir%20with%20spaces/zipfstest.zip"); Path dir = Paths.get("C:\\dir with spaces"); if(Files.exists(dir) && Files.isDirectory(dir)) { try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) {} } } } 

当我执行此代码(Windows,JDK7u2,x32和x64)时,我得到以下exception:

 java.lang.IllegalArgumentException: Illegal character in path at index 12: file:/c:/dir with spaces/zipfstest.zip at com.sun.nio.zipfs.ZipFileSystemProvider.uriToPath(ZipFileSystemProvider.java:87) at com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:107) at java.nio.file.FileSystems.newFileSystem(FileSystems.java:322) at java.nio.file.FileSystems.newFileSystem(FileSystems.java:272) 

如果我使用+而不是%20作为空格转义字符,则会抛出另一个exception:

 java.nio.file.NoSuchFileException: c:\dir+with+spaces\zipfstest.zip at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79) at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97) at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102) at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:229) at java.nio.file.spi.FileSystemProvider.newOutputStream(FileSystemProvider.java:430) at java.nio.file.Files.newOutputStream(Files.java:170) at com.sun.nio.zipfs.ZipFileSystem.(ZipFileSystem.java:116) at com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:117) at java.nio.file.FileSystems.newFileSystem(FileSystems.java:322) at java.nio.file.FileSystems.newFileSystem(FileSystems.java:272) 

我可能会遗漏一些非常明显的东西,但这是否表示提供的ZIP / JAR文件系统提供程序存在问题?

编辑:

另一个基于File对象的用例,如coments中所要求的:

 import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URI; import java.nio.file.FileSystems; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; public class Test { public static void main(String[] args) throws UnsupportedEncodingException { try { File zip = new File("C:\\dir with spaces\\file.zip"); URI uri = URI.create("jar:" + zip.toURI().toURL()); Map env = new HashMap(); env.put("create", "true"); if(zip.getParentFile().exists() && zip.getParentFile().isDirectory()) { FileSystems.newFileSystem(uri, env); } } catch (Exception ex) { Logger.getAnonymousLogger().log(Level.SEVERE, null, ex); System.out.println(); } } } 

exception再次抛出:

 java.lang.IllegalArgumentException: Illegal character in path at index 12: file:/C:/dir with spaces/file.zip at com.sun.nio.zipfs.ZipFileSystemProvider.uriToPath(ZipFileSystemProvider.java:87) at com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:107) at java.nio.file.FileSystems.newFileSystem(FileSystems.java:322) at java.nio.file.FileSystems.newFileSystem(FileSystems.java:272) 

实际上,进一步的分析似乎表明ZipFileSystemProvider存在问题。 该类中包含的uriToPath(URI uri)方法执行以下代码段:

 String spec = uri.getSchemeSpecificPart(); int sep = spec.indexOf("!/"); if (sep != -1) spec = spec.substring(0, sep); return Paths.get(new URI(spec)).toAbsolutePath(); 

从URI.getSchemeSpecificPart()的JavaDocs中我们可以看到以下内容:

此方法返回的字符串等于getRawSchemeSpecificPart方法返回的字符串,但所有转义的八位字节序列都将被解码

然后将相同的字符串作为参数传递回新的URI()构造函数。 由于任何转义的八位字节都被getSchemeSpecificPart()解除转义,如果原始URI包含任何转义字符,它们将不会传播到新的URI – 因此是例外。

一个潜在的解决方法 – 遍历所有可用的文件系统提供程序,并获得对spec等于“jar”的引用。 然后使用它来创建基于路径的新文件系统。

这是Java 7中的一个错误,它已在Java 8中标记为已修复(请参阅错误ID 7156873 )。 修复程序也应该向后移植到Java 7,但目前尚未确定哪个更新将具有它(请参阅错误ID 8001178 )。

jar:URI应该在其特定于方案的部分中具有转义的zip-URI,因此你的jar:URI是完全错误的 – 它应该正确地进行双重转义,因为jar:scheme由主机URI组成!/和当地的道路。

但是,这种转义只是暗示,而不是由JarURLConnection中的最小URL“规范” 表示 。 我同意JRE中提出的错误,它应该仍然接受单一转义,尽管这可能会导致一些奇怪的边缘情况得不到支持。

正如另一个答案中的tornike和evermean所指出的那样 ,最简单的方法是使用FileSystems.newFileSystem(path,null) – 但是当你想传递和env并说“create”= true时,这不起作用。

相反,使用基于组件的构造函数创建jar:URI:

 URI jar = new URI("jar", path.toUri().toString(), null); 

这将正确编码特定于方案的部分。

作为JUnit测试,它还确认这是从Path打开时使用的转义:

 @Test public void jarWithSpaces() throws Exception { Path path = Files.createTempFile("with several spaces", ".zip"); Files.delete(path); // Will fail with FileSystemNotFoundException without env: //FileSystems.newFileSystem(path, null); // Neither does this work, as it does not double-escape: // URI jar = URI.create("jar:" + path.toUri().toASCIIString()); URI jar = new URI("jar", path.toUri().toString(), null); assertTrue(jar.toASCIIString().contains("with%2520several%2520spaces")); Map env = new HashMap<>(); env.put("create", "true"); try (FileSystem fs = FileSystems.newFileSystem(jar, env)) { URI root = fs.getPath("/").toUri(); assertTrue(root.toString().contains("with%2520several%2520spaces")); } // Reopen from now-existing Path to check that the URI is // escaped in the same way try (FileSystem fs = FileSystems.newFileSystem(path, null)) { URI root = fs.getPath("/").toUri(); //System.out.println(root.toASCIIString()); assertTrue(root.toString().contains("with%2520several%2520spaces")); } } 

(我用“with \ u2301unicode \ u263bhere”进行了类似的测试,检查我是否需要使用.toASCIIString())

创建文件系统有两种方法:

FileSystem fs = FileSystems.newFileSystem(uri, env);

FileSystem fs = FileSystems.newFileSystem(zipfile, null);

当文件名中有空格以及上述用于创建uri的解决方案时。 如果您使用不使用uri作为参数的其他方法,它也可以工作。