我一直在测试所有可能的变体和排列,但我似乎无法构建一个带有zip / jar方案的FileSystemProvider,用于包含空格的路径(URI)。 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<String, String> 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)时,我得到以下异常:
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作为空格转义字符,则抛出另一个异常:
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.<init>(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<String, String> 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();
}
}
}
异常再次抛出:
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)
答案 0 :(得分:6)
实际上,进一步分析似乎表明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”的引用。然后使用它来创建一个基于路径的新文件系统。
答案 1 :(得分:5)
这是Java 7中的一个错误,它已在Java 8中标记为已修复(请参阅Bug ID 7156873)。修复程序也应该向后移植到Java 7,但目前尚未确定哪个更新将具有它(请参阅Bug ID 8001178)。
答案 2 :(得分:4)
jar:URI应该在其特定于方案的部分中具有转义的zip-URI,因此你的jar:URI完全错误 - 它应该正确地进行双重转义,因为jar:scheme由主机URI组成, !/和本地路径。
但是,这种转义只是暗示,而不是由JarURLConnection中的最小URL“规范”表达。我同意JRE中提出的错误,它应该仍然接受单一转义,尽管这可能导致一些奇怪的边缘情况得不到支持。
在另一个答案中,作为pointed out by tornike and evermean,最简单的方法是执行FileSystems.newFileSystem(path,null) - 但是当你想传递和使用“create”= true时,这不起作用。
相反,使用基于组件的构造函数创建jar:URI:
URI jar = new URI("jar", path.toUri().toString(), null);
这将正确编码特定于方案的部分。
作为JUnit测试,它还确认这是从路径打开时使用的转义:
@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<String, Object> 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())
答案 3 :(得分:3)
创建文件系统有两种方法:
FileSystem fs = FileSystems.newFileSystem(uri, env);
FileSystem fs = FileSystems.newFileSystem(zipfile, null);
当文件名中有空格以及上述用于创建uri的解决方案时。如果您使用不以uri作为参数的其他方法,它也可以工作。