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


143

是否有API可以获取类路径资源(例如,我将从中获得的资源Class.getResource(String)java.nio.file.Path?理想情况下,我想将新的PathAPI与类路径资源一起使用。


3
好了,走了很长一段路(双关语Paths.get(URI)意味),然后有了URL.toURI(), and last getResource(),它返回了一个URL。您可能可以将它们链接在一起。还没有尝试过。
NilsH 2013年

Answers:


174

这对我有用:

return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());

7
@VGR,如果.jar文件中的资源可以尝试此方法`Resource resource = new ClassPathResource(“ usage.txt”); 读者的BufferedReader =新的BufferedReader(新的InputStreamReader(resource.getInputStream()));`请参阅stackoverflow.com/questions/25869428/...
zhuguowei

8
@zhuguowei这是特定于Spring的方法。当不使用Spring时,它根本不起作用。
Ryan J. McDonough

2
如果您的应用程序不依赖系统类加载器,则应为Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
ThrawnCA '19

27

猜测要执行的操作是,对来自类路径(可能是来自jar内)的资源调用Files.lines(...)。

由于Oracle通过使getResource如果位于jar文件中而不使其返回可用路径来混淆路径何时是路径的概念,因此您需要执行以下操作:

Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();

1
我不知道在您的情况下是否需要前面的“ /”,但在我的情况下class.getResource需要一个斜杠,但是在以斜杠getSystemResourceAsStream为前缀时找不到该文件。
亚当

11

最通用的解决方案如下:

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

主要的障碍是应对两种可能性,要么是拥有一个我们应该使用但不关闭的现有文件系统(例如使用fileURI或Java 9的模块存储),要么必须自己打开并因此安全地关闭文件系统(例如zip / jar文件)。

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

可以在上面提到的所有Java版本中使用它,例如,java.langPaths 列出软件包的内容(在示例中),如下所示:

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

做同样的事情。


Java 9的模块系统的最终版本破坏了上面的代码示例。该JRE返回不一致的路径/java.base/java/lang/Object.class进行Object.class.getResource("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);
    }
});

然后,它将再次适用于所有版本和存储方法。


1
这个解决方案很棒!我可以确认这适用于目录类路径和jar类路径中的所有资源(文件,目录)。这绝对是Java 7+中应该复制大量资源的方式。
Mitchell Skaggs

10

事实证明,您可以在内置的Zip文件系统提供程序的帮助下执行此操作。但是,直接Paths.get将资源URI传递给是行不通的;相反,必须首先为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的工作对象,就像Holger的回答是如何做到的。打开ZipFS FileSystem足够长的时间,以便工作人员可以对Path进行任何操作(只要工作人员不尝试存储Path对象供以后使用),然后关闭FileSystem。


11
注意新创建的fs。使用同一jar进行的第二次调用将引发异常,提示已存在文件系统。尝试try(FileSystem fs = ...){return fs.getPath(entryName);}会更好,或者如果要缓存此内容,请执行更高级的处理。以目前的形式存在风险。
提升者成本2013年

3
除了可能未关闭的新文件系统的问题外,有关方案之间的关系的假设以及打开新文件系统的必要性以及对URI内容的困惑也限制了解决方案的实用性。我设置了一个新的答案,该答案显示了一种通用方法,该方法可以简化操作并同时处理新的方案,例如新的Java 9类存储。当应用程序中的其他人已经打开文件系统(或对该同一个jar调用该方法两次)时,它也可以工作…
Holger

根据此解决方案的使用情况,非封闭式newFileSystem可导致多个资源永远挂在开放式环境中。尽管@raisercostin附录在尝试创建已创建的文件系统时避免了该错误,但是如果尝试使用返回的内容Path,则会得到ClosedFileSystemException。@Holger的回复对我来说效果很好。
何塞·安迪亚斯(JoséAndias)

我不会关闭FileSystem。如果您从Jar中加载资源,然后创建所需资源FileSystem- FileSystem还将允许您从同一Jar中加载其他资源。同样,一旦创建了新的,FileSystem您就可以尝试再次使用来加载资源,Paths.get(Path)并且实现将自动使用new FileSystem
NS du Toit

也就是说,您不必#getPath(String)FileSystem对象上使用方法。
NS du Toit

5

我写了一个小的辅助方法来Paths从您的类资源中读取。使用它非常方便,因为它只需要引用您已存储资源的类以及资源本身的名称。

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

1

您不能从jar文件内部的资源创建URI。您可以简单地将其写入临时文件,然后使用它(java8):

Path path = File.createTempFile("some", "address").toPath();
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);

1

在Java8中使用NIO从资源文件夹读取文件

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.