【问题标题】:java.nio.file.Path for a classpath resource类路径资源的 java.nio.file.Path
【发布时间】:2013-03-20 17:41:55
【问题描述】:

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

【问题讨论】:

  • 好吧,走长路(双关语),你有Paths.get(URI),然后是'URL.toURI(), and last getResource()`,它返回一个URL。您也许可以将它们链接在一起。不过没试过。

标签: java java-7 nio2


【解决方案1】:

最通用的解决方案如下:

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 中,处理两种情况,然后在第二种情况下安全关闭,并且从 Java 7 工作到 Java 18。它在打开之前探测是否已经有一个打开的文件系统一个新的,因此它也适用于应用程序的另一个组件已经为同一个 zip/jar 文件打开文件系统的情况。

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

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 的模块系统has broken 上述代码示例的最终版本。从 9 到 12 的 Java 版本不一致地返回路径 /java.base/java/lang/Object.classPaths.get(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);
    }
});

然后,它将再次适用于所有版本和存储方法。从 JDK 13 开始,不再需要这种变通方法。

【讨论】:

  • 这个解决方案效果很好!我可以确认这适用于目录类路径和 jar 类路径中的所有资源(文件、目录)。这绝对是在 Java 7+ 中复制大量资源的方式。
【解决方案2】:

事实证明,借助内置的 Zip File System provider,您可以做到这一点。但是,将资源 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 对象供以后使用),然后关闭文件系统。

【讨论】:

  • 小心新创建的fs。使用同一个 jar 的第二次调用将引发异常,抱怨已经存在的文件系统。最好做 try(FileSystem fs=...){return fs.getPath(entryName);} 或者如果你想让这个缓存做更高级的处理。目前的形式是有风险的。
  • 除了可能未关闭的新文件系统的问题之外,关于方案之间关系的假设和打开新文件系统的必要性以及对 URI 内容的困惑限制了解决方案的有用性。我设置了一个new answer,它展示了一种通用方法,它可以简化操作并同时处理新的方案,如新的 Java 9 类存储。当应用程序中的其他人已经打开了文件系统(或者该方法为同一个 jar 调用了两次)时,它也可以工作......
  • 根据此解决方案的使用情况,未关闭的newFileSystem 可能导致多个资源永远处于打开状态。尽管@raisercostin 附录在尝试创建已创建的文件系统时避免了该错误,但如果您尝试使用返回的Path,您将获得ClosedFileSystemException。 @Holger 回复对我来说效果很好。
  • 我不会关闭FileSystem。如果您从 Jar 加载资源,然后创建所需的 FileSystem - FileSystem 还允许您从同一个 Jar 加载其他资源。此外,一旦您创建了新的FileSystem,您可以尝试使用Paths.get(Path) 再次加载资源,实现将自动使用新的FileSystem
  • 即您不必在FileSystem 对象上使用#getPath(String) 方法。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      您需要定义文件系统以从 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)) {
                  ....
              }
          }
      

      【讨论】:

        【解决方案6】:

        猜测您想要做的是在来自类路径的资源上调用 Files.lines(...) - 可能来自 jar 中。

        由于 Oracle 通过不让 getResource 返回可用路径(如果它位于 jar 文件中)来混淆路径何时是路径的概念,因此您需要做的是这样的事情:

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

        【讨论】:

        • 我不知道在您的情况下是否需要前面的“/”,但在我的情况下,class.getResource 需要斜杠,但 getSystemResourceAsStream 在前缀为斜杠时找不到文件.
        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

          这个对我有用:

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

          【讨论】:

          • @VGR 如果 .jar 文件中的资源可以尝试这个 ` Resource resource = new ClassPathResource("usage.txt"); BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));`请看stackoverflow.com/questions/25869428/…
          • @zhguuowei 这是 Spring 特有的方法。当不使用 Spring 时,它根本不起作用。
          • 如果你的应用不依赖系统类加载器,应该是Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
          猜你喜欢
          • 2016-01-11
          • 2010-12-26
          • 2010-12-08
          • 2011-12-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-27
          • 2015-11-09
          相关资源
          最近更新 更多