是否有一个 API 来获取类路径资源(例如,我从 Class.getResource(String)获取的)作为 java.nio.file.Path?理想情况下,我希望使用带有类路径资源的新的 Path API。
Class.getResource(String)
java.nio.file.Path
Path
事实证明,在内置 压缩文件系统提供程序的帮助下,您可以做到这一点。但是,直接将资源 URI 传递给 Paths.get是不行的; 相反,必须首先为 jar URI 创建一个没有条目名称的 zip 文件系统,然后引用该文件系统中的条目:
Paths.get
static Path resourceToPath(URL resource) throws IOException, URISyntaxException { Objects.requireNonNull(resource, "Resource URL cannot be null"); URI uri = resource.toURI(); String scheme = uri.getScheme(); if (scheme.equals("file")) { return Paths.get(uri); } if (!scheme.equals("jar")) { throw new IllegalArgumentException("Cannot convert to Path: " + uri); } String s = uri.toString(); int separator = s.indexOf("!/"); String entryName = s.substring(separator + 2); URI fileURI = URI.create(s.substring(0, separator)); FileSystem fs = FileSystems.newFileSystem(fileURI, Collections.<String, Object>emptyMap()); return fs.getPath(entryName); }
更新:
有人正确地指出,上面的代码包含资源泄漏,因为该代码打开了一个新的 FileSystem 对象,但从未关闭它。最好的方法是传递一个类似 Consumer 的 worker 对象,就像 Holger 的回答一样。打开 ZipFS FileSystem,时间足够工作人员对 Path 做任何需要的事情(只要工作人员不试图存储 Path 对象供以后使用) ,然后关闭 FileSystem。
这个适合我:
return Path.of(ClassLoader.getSystemResource(resourceName).toURI());
我编写了一个小型助手方法来从类资源中读取 Paths。使用它非常方便,因为它只需要存储资源的类的引用以及资源本身的名称。
Paths
public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException { URL url = resourceClass.getResource(resourceName); return Paths.get(url.toURI()); }
假设您想要做的是对来自类路径的资源调用 Files.lines(...)——可能来自 jar 内部。
Files.lines(...)
因为 Oracle 没有让 getResource返回一个可用的路径(如果它驻留在 jar 文件中的话) ,这使得 Path是 Path的概念变得复杂起来,所以您需要做的是这样的事情:
getResource
Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
您需要像 https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html中提到的那样定义从 jar 文件中读取资源的 Filessystem。我成功地从 jar 文件中读取资源,代码如下:
Map<String, Object> env = new HashMap<>(); try (FileSystem fs = FileSystems.newFileSystem(uri, env)) { Path path = fs.getPath("/path/myResource"); try (Stream<String> lines = Files.lines(path)) { .... } }
最普遍的解决办法如下:
interface IOConsumer<T> { void accept(T t) throws IOException; } public static void processRessource(URI uri, IOConsumer<Path> action) throws IOException{ try { Path p=Paths.get(uri); action.accept(p); } catch(FileSystemNotFoundException ex) { try(FileSystem fs = FileSystems.newFileSystem( uri, Collections.<String,Object>emptyMap())) { Path p = fs.provider().getPath(uri); action.accept(p); } } }
主要的障碍是处理两种可能性,要么拥有一个我们应该使用但不关闭的现有文件系统(如 file URI 或 Java 9的模块存储) ,要么必须自己打开并因此安全地关闭文件系统(如 zip/jar 文件)。
file
因此,上面的解决方案将实际操作封装在 interface中,处理两种情况,然后在第二种情况下安全地关闭,并从 Java7到 Java18工作。它在打开一个新文件系统之前探测是否已经有一个打开的文件系统,因此在应用程序的另一个组件已经为相同的 zip/jar 文件打开了一个文件系统的情况下,它也可以工作。
interface
它可以在上面命名的所有 Java 版本中使用,例如,将包的内容(示例中的 java.lang)列为 Path,如下所示:
java.lang
processRessource(Object.class.getResource("Object.class").toURI(),new IOConsumer<Path>(){ public void accept(Path path) throws IOException { try(DirectoryStream<Path> ds = Files.newDirectoryStream(path.getParent())) { for(Path p: ds) System.out.println(p); } } });
使用 Java8或更新的版本,您可以使用 lambda 表达式或方法引用来表示实际的操作,例如。
processRessource(Object.class.getResource("Object.class").toURI(), path -> { try(Stream<Path> stream = Files.list(path.getParent())) { stream.forEach(System.out::println); } });
做同样的事。
Java9模块系统 已经坏了的最终版本就是上面的代码示例。从9到12的 Java 版本不一致地返回 Paths.get(Object.class.getResource("Object.class"))的路径 /java.base/java/lang/Object.class,而它应该是 /modules/java.base/java/lang/Object.class。当父路径被报告为不存在时,可以通过预先设置缺少的 /modules/来解决这个问题:
Paths.get(Object.class.getResource("Object.class"))
/java.base/java/lang/Object.class
/modules/java.base/java/lang/Object.class
/modules/
processRessource(Object.class.getResource("Object.class").toURI(), path -> { Path p = path.getParent(); if(!Files.exists(p)) p = p.resolve("/modules").resolve(p.getRoot().relativize(p)); try(Stream<Path> stream = Files.list(p)) { stream.forEach(System.out::println); } });
然后,它将再次与所有版本和存储方法一起工作。从 JDK 13开始,就不再需要这种解决方案了。
不能从 jar 文件内部的资源创建 URI。您可以简单地将它写入临时文件,然后使用它(java8) :
Path path = File.createTempFile("some", "address").toPath(); Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);
在 java8中,使用 NIO 从资源文件夹读取 File
public static String read(String fileName) { Path path; StringBuilder data = new StringBuilder(); Stream<String> lines = null; try { path = Paths.get(Thread.currentThread().getContextClassLoader().getResource(fileName).toURI()); lines = Files.lines(path); } catch (URISyntaxException | IOException e) { logger.error("Error in reading propertied file " + e); throw new RuntimeException(e); } lines.forEach(line -> data.append(line)); lines.close(); return data.toString(); }