簡體   English   中英

類路徑資源的 java.nio.file.Path

[英]java.nio.file.Path for a classpath resource

是否有一個 API 來獲取類路徑資源(例如我從Class.getResource(String) )作為java.nio.file.Path 理想情況下,我想使用帶有類路徑資源的新奇Path API。

這個對我有用:

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

猜測您想要做的是在來自類路徑的資源上調用 Files.lines(...) - 可能來自 jar 。

由於 Oracle 通過不讓 getResource 返回可用路徑(如果它駐留在 jar 文件中)來復雜化 Path 何時是 Path 的概念,因此您需要做的是這樣的事情:

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

因此,上面的解決方案將實際操作封裝在一個interface ,處理這兩種情況,然后在第二種情況下安全關閉,並且從 Java 7 到 Java 10 工作。它在打開一個新文件系統之前先探測是否已經有一個打開的文件系統,所以它也適用於應用程序的另一個組件已經為同一個 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);
        }
    }
});

在 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 不一致地為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);
    }
});

然后,它將再次適用於所有版本和存儲方法。

事實證明,在內置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 對象但從未關閉它。 最好的方法是傳遞一個類 Consumer 的工作對象,就像 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());
}  

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

您不能從 jar 文件內的資源創建 URI。 您可以簡單地將其寫入臨時文件,然后使用它(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)) {
            ....
        }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM