是否有一个 API 可以将类路径资源(例如我从中得到的Class.getResource(String)
)作为java.nio.file.Path
? 理想情况下,我想将花哨的新Path
API 与类路径资源一起使用。
8 回答
这个对我有用:
return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
猜测您想要做的是调用Files.lines(...)
来自类路径的资源 - 可能来自 jar 中。
由于 Oracle 混淆了 a 何时Path
为 a的概念,如果它位于 jar 文件中,Path
则不返回可用路径,因此您需要做的是这样的事情:getResource
Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
最通用的解决方案如下:
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 文件)。
因此,上面的解决方案将实际操作封装在 an 中interface
,处理这两种情况,在第二种情况下安全关闭,并且适用于 Java 7 到 Java 18。它在打开新文件系统之前探测是否已经有一个打开的文件系统,所以它也适用于应用程序的另一个组件已经为同一个 zip/jar 文件打开文件系统的情况。
它可以用于上面提到的所有 Java 版本,例如将包的内容(java.lang
在示例中)列为Path
s,如下所示:
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);
}
}
});
在 Java 8 或更新版本中,您可以使用 lambda 表达式或方法引用来表示实际操作,例如
processRessource(Object.class.getResource("Object.class").toURI(), path -> {
try(Stream<Path> stream = Files.list(path.getParent())) {
stream.forEach(System.out::println);
}
});
做同样的事情。
The final release of Java 9’s module system has broken the above code example. The Java versions from 9 to 12 inconsistently return the path /java.base/java/lang/Object.class
for Paths.get(Object.class.getResource("Object.class"))
whereas it should be /modules/java.base/java/lang/Object.class
. This can be fixed by prepending the missing /modules/
when the parent path is reported as non-existent:
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);
}
});
Then, it will again work with all versions and storage methods. Starting with JDK 13, this work-around is not necessary anymore.
事实证明,您可以在内置Zip 文件系统提供程序的帮助下做到这一点。但是,将资源 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 对象但从未关闭它。最好的方法是传递一个像消费者一样的工作对象,就像 Holger 的答案是如何做到的。打开 ZipFS 文件系统,让工作人员对 Path 做任何需要做的事情(只要工作人员不尝试存储 Path 对象供以后使用),然后关闭文件系统。
我写了一个小助手方法来读取Paths
你的类资源。它使用起来非常方便,因为它只需要您存储资源的类的引用以及资源本身的名称。
public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException {
URL url = resourceClass.getResource(resourceName);
return Paths.get(url.toURI());
}
Read a File from resources folder using NIO, in java8
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();
}
You can not create URI from resources inside of the jar file. You can simply write it to the temp file and then use it (java8):
Path path = File.createTempFile("some", "address").toPath();
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);
您需要定义文件系统以从 jar 文件中读取资源,如https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html中所述。我成功地使用以下代码从 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)) {
....
}
}