一个类路径资源的 java.nio.file. Path

是否有一个 API 来获取类路径资源(例如,我从 Class.getResource(String)获取的)作为 java.nio.file.Path?理想情况下,我希望使用带有类路径资源的新的 Path API。

153413 次浏览

事实证明,在内置 压缩文件系统提供程序的帮助下,您可以做到这一点。但是,直接将资源 URI 传递给 Paths.get是不行的; 相反,必须首先为 jar URI 创建一个没有条目名称的 zip 文件系统,然后引用该文件系统中的条目:

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。使用它非常方便,因为它只需要存储资源的类的引用以及资源本身的名称。

public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException {
URL url = resourceClass.getResource(resourceName);
return Paths.get(url.toURI());
}

假设您想要做的是对来自类路径的资源调用 Files.lines(...)——可能来自 jar 内部。

因为 Oracle 没有让 getResource返回一个可用的路径(如果它驻留在 jar 文件中的话) ,这使得 PathPath的概念变得复杂起来,所以您需要做的是这样的事情:

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 文件)。

因此,上面的解决方案将实际操作封装在 interface中,处理两种情况,然后在第二种情况下安全地关闭,并从 Java7到 Java18工作。它在打开一个新文件系统之前探测是否已经有一个打开的文件系统,因此在应用程序的另一个组件已经为相同的 zip/jar 文件打开了一个文件系统的情况下,它也可以工作。

它可以在上面命名的所有 Java 版本中使用,例如,将包的内容(示例中的 java.lang)列为 Path,如下所示:

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/来解决这个问题:

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();
}