在jar文件中访问Java文件导致java.nio.file.FileSystemNotFoundException

71

在我的Java应用程序中尝试将一些文件从我的jar文件复制到临时目录时,抛出以下异常:

java.nio.file.FileSystemNotFoundException
    at com.sun.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:171)
    at com.sun.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:157)
    at java.nio.file.Paths.get(Unknown Source)
    at com.sora.util.walltoggle.pro.WebViewPresentation.setupTempFiles(WebViewPresentation.java:83)
   ....

这是我setupTempFiles的一小部分代码(带有行号):

81. URI uri = getClass().getResource("/webViewPresentation").toURI();
//prints: URI->jar:file:/C:/Users/Tom/Dropbox/WallTogglePro.jar!/webViewPresentation
82. System.out.println("URI->" + uri );
83. Path source = Paths.get(uri);

webViewPresentation目录位于我的jar根目录下:

enter image description here

只有当我将应用程序打包为jar时才会出现此问题,在Eclipse中进行调试没有问题。 我怀疑这与这个bug有关,但我不确定如何解决这个问题。

感谢任何帮助。

如果有关系:

我使用的是Java 8版本1.8.0-b132

Windows 7 Ult. x64


你必须以稍微不同的方式处理JAR文件,这里有一个问题可以参考https://dev59.com/w2435IYBdhLWcg3w20B8 - onesixtyfourth
答案中使用的方法将单个文件读取为“Stream”,我想复制整个目录。我已经添加了一张截图以澄清。 - tom91136
也许您还想使用Files.createTempDirectory - SebastianH
是的,我在我的 main() 函数中做了那件事。 - tom91136
在IDE(Eclipse)中运行时,资源实际上是一个文件。如果您打包应用程序并尝试在IDE之外运行它,则问题将浮出水面。 - cquezel
4个回答

65

FileSystemNotFoundException 表示文件系统无法自动创建,您在此处也没有创建它。

根据您的 URI,您应该针对 ! 进行拆分,在其前面的部分打开文件系统,然后从 ! 后面的部分获取路径:

final Map<String, String> env = new HashMap<>();
final String[] array = uri.toString().split("!");
final FileSystem fs = FileSystems.newFileSystem(URI.create(array[0]), env);
final Path path = fs.getPath(array[1]);

请注意,当您完成使用FileSystem后,应该.close()它。


5
是的,这是一些文档不太全面的东西...... 如果这些路径不属于相同的文件系统提供程序,您应该使用 .resolve().relativize() 对其他路径的 .toString() 值进行操作... 我也曾受到过这方面的困扰(我正在开发一个基于FTP的文件系统实现)。 - fge
1
不确定是否更好,但[在这里](http://fgaliegue.blogspot.com/2014/03/working-with-java-7-file-api-recursive.html)给你 :) - fge
2
请注意,自那时以来,我已经开发了一些可能会引起您兴趣的东西;请参见这里 - fge
1
@8odoros 最好使用 .replaceFirst();你只想替换找到的第一个出现,而不是所有的。 - fge
2
这种方法可以处理嵌套的JAR包吗,例如 jar:file:outer.jar!/inner.jar!/file.txt - jaco0646
显示剩余9条评论

12

被接受的答案并不是最好的,因为当您在IDE中启动应用程序或资源是静态的且存储在类中时它不起作用!更好的解决方案在获取资源文件夹中的文件时出现java.nio.file.FileSystemNotFoundException中提出。

InputStream in = getClass().getResourceAsStream("/webViewPresentation");
byte[] data = IOUtils.toByteArray(in);

IOUtils来自Apache commons-io库。

但是如果你已经在使用Spring,并且想要一个文本文件,你可以将第二行代码改为:

StreamUtils.copyToString(in, Charset.defaultCharset());

StreamUtils.copyToByteArray也存在。


6
楼主想要获取一个“路径”(可能是为了利用NIO的新异步处理能力),而不是流或字符串,因此这不是答案。 - kaqqao
它可能不是问题的答案,但确实路径/URI解决方案有两个不可用的原因:如果资源未打包,则无法工作;如果同时从JAR中打开两个资源,则无法工作 - 您只能打开文件系统一次...但是,您如何一般性地知道它是否为同一个文件系统?需要进行大量测试。此处的答案显示了一个简单且有效的解决方案。 - Claude

10
这可能是一种hack方法,但以下方法对我有效:

URI uri = getClass().getResource("myresourcefile.txt").toURI();

if("jar".equals(uri.getScheme())){
    for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
        if (provider.getScheme().equalsIgnoreCase("jar")) {
            try {
                provider.getFileSystem(uri);
            } catch (FileSystemNotFoundException e) {
                // in this case we need to initialize it first:
                provider.newFileSystem(uri, Collections.emptyMap());
            }
        }
    }
}
Path source = Paths.get(uri);

这是利用ZipFileSystemProvider内部存储已通过URI打开的文件系统列表的事实。

6
很奇怪,Paths.get(URI)没有处理jar文件路径的能力... - Joseph Tesfaye

0
如果您正在使用Spring框架库,那么这个问题有一个简单的解决方案。
根据要求,我们想要读取webViewPresentation; 我可以通过以下代码解决同样的问题:
URI uri = getClass().getResource("/webViewPresentation").toURI();
FileSystems.getDefault().getPath(new UrlResource(uri).toString());

1
似乎无法处理嵌套的JAR文件或BOOT-INF/classes - barfuin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接