java.nio.file.Path cho tài nguyên classpath


143

Có API để lấy tài nguyên đường dẫn (ví dụ: tôi nhận được gì Class.getResource(String)) java.nio.file.Pathkhông? Lý tưởng nhất là tôi muốn sử dụng các PathAPI mới lạ mắt với tài nguyên đường dẫn.


3
Chà, đi theo con đường dài (ý định chơi chữ), bạn có Paths.get(URI), sau đó là hungURL.toURI () , and last getResource () `trả về a URL. Bạn có thể xâu chuỗi chúng lại với nhau. Haven Xnt đã cố gắng mặc dù.
NilsH

Câu trả lời:


174

Cái này hoạt động với tôi:

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

7
@VGR nếu tài nguyên trong tệp .jar có thể thử `Resource resource = new ClassPathResource (" used.txt "); BufferedReader đọc = BufferedReader mới (InputStreamReader mới (resource.getInputStream ())); `xin vui lòng xem stackoverflow.com/questions/25869428/...
zhuguowei

8
@zhuguowei đó là một cách tiếp cận dành riêng cho mùa xuân. Nó hoàn toàn không hoạt động khi Spring không được sử dụng.
Ryan J. McDonough

2
Nếu ứng dụng của bạn không phụ thuộc vào trình nạp lớp hệ thống, thì nó phải làThread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
ThrawnCA

27

Đoán rằng những gì bạn muốn làm, hãy gọi Files.lines (...) trên một tài nguyên đến từ đường dẫn lớp - có thể từ trong một jar.

Vì Oracle đã xác định khái niệm khi nào Đường dẫn là Đường dẫn bằng cách không tạo getResource trả về một đường dẫn có thể sử dụng được nếu nó nằm trong tệp jar, điều bạn cần làm là như thế này:

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

1
tôi có biết "/" trước đó trong trường hợp của bạn không, nhưng trong trường hợp của tôi class.getResourceyêu cầu một dấu gạch chéo nhưng getSystemResourceAsStreamkhông thể tìm thấy tệp khi được thêm tiền tố bằng dấu gạch chéo.
Adam

11

Giải pháp chung nhất như sau:

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

Trở ngại chính là đối phó với hai khả năng, có một hệ thống tệp hiện có mà chúng ta nên sử dụng, nhưng không đóng (như với fileURI hoặc lưu trữ mô-đun của Java 9) hoặc phải tự mở và do đó tự đóng hệ thống tệp một cách an toàn (như tập tin zip / jar).

Do đó, giải pháp trên đóng gói hành động thực tế trong một interface, xử lý cả hai trường hợp, đóng an toàn sau đó trong trường hợp thứ hai và hoạt động từ Java 7 đến Java 10. Nó thăm dò xem đã có hệ thống tệp mở trước khi mở một hệ thống tệp mới chưa, vì vậy nó cũng hoạt động trong trường hợp một thành phần khác trong ứng dụng của bạn đã mở một hệ thống tệp cho cùng một tệp zip / jar.

Nó có thể được sử dụng trong tất cả các phiên bản Java có tên ở trên, ví dụ để liệt kê nội dung của gói ( java.langtrong ví dụ) dưới dạng Paths, như thế này:

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

Với Java 8 hoặc mới hơn, bạn có thể sử dụng biểu thức lambda hoặc tham chiếu phương thức để thể hiện hành động thực tế, ví dụ:

processRessource(Object.class.getResource("Object.class").toURI(), path -> {
    try(Stream<Path> stream = Files.list(path.getParent())) {
        stream.forEach(System.out::println);
    }
});

để làm cái tương tự.


Bản phát hành cuối cùng của hệ thống mô-đun của Java 9 đã phá vỡ ví dụ mã trên. JRE nhất quán trả về con đường /java.base/java/lang/Object.classcho Object.class.getResource("Object.class")trong khi nó phải được /modules/java.base/java/lang/Object.class. Điều này có thể được khắc phục bằng cách thêm vào phần còn thiếu /modules/khi đường dẫn cha được báo cáo là không tồn tại:

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

Sau đó, nó sẽ lại hoạt động với tất cả các phiên bản và phương thức lưu trữ.


1
Giải pháp này hoạt động rất tốt! Tôi có thể xác nhận rằng điều này hoạt động với tất cả các tài nguyên (tệp, thư mục) trong cả hai đường dẫn thư mục và đường dẫn jar. Đây chắc chắn là cách sao chép nhiều tài nguyên nên được thực hiện trong Java 7+.
Mitchell Skagss

10

Hóa ra bạn có thể làm điều này, với sự trợ giúp của nhà cung cấp Hệ thống tệp Zip tích hợp . Tuy nhiên, việc truyền trực tiếp URI tài nguyên để Paths.getkhông hoạt động; thay vào đó, trước tiên, người ta phải tạo một hệ thống tệp zip cho URI jar mà không có tên mục nhập, sau đó tham khảo mục trong hệ thống tệp đó:

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

Cập nhật:

Điều đó đã được chỉ ra một cách đúng đắn rằng đoạn mã trên có chứa rò rỉ tài nguyên, vì mã mở một đối tượng FileSystem mới nhưng không bao giờ đóng nó. Cách tiếp cận tốt nhất là vượt qua một đối tượng công nhân giống như Người tiêu dùng, giống như cách câu trả lời của Holger thực hiện. Mở ZipFS FileSystem đủ lâu để công nhân có thể làm bất cứ điều gì cần làm với Đường dẫn (miễn là công nhân không cố lưu trữ đối tượng Đường dẫn để sử dụng sau), sau đó đóng Hệ thống tệp.


11
Hãy cẩn thận với fs mới được tạo. Một cuộc gọi thứ hai sử dụng cùng một jar sẽ đưa ra một ngoại lệ phàn nàn về một hệ thống tập tin đã tồn tại. Sẽ tốt hơn để thử (FileSystem fs = ...) {return fs.getPath (entryName);} hoặc nếu bạn muốn bộ nhớ cache này xử lý nâng cao hơn. Trong hình thức hiện tại là rủi ro.
raisercostin

3
Bên cạnh vấn đề về hệ thống tệp mới không có khả năng đóng, các giả định về mối quan hệ giữa các lược đồ và sự cần thiết phải mở một hệ thống tệp mới và đánh đố với nội dung URI làm hạn chế tính hữu ích của giải pháp. Tôi đã thiết lập một câu trả lời mới cho thấy một cách tiếp cận chung giúp đơn giản hóa hoạt động và xử lý các lược đồ mới như lưu trữ lớp Java 9 mới cùng một lúc. Nó cũng hoạt động khi có người khác trong ứng dụng đã mở hệ thống tệp (hoặc phương thức được gọi hai lần cho cùng một tệp))
Holger

Tùy thuộc vào việc sử dụng giải pháp này, việc không đóng newFileSystemcó thể dẫn đến nhiều tài nguyên treo xung quanh mở mãi mãi. Mặc dù @raisercostin addendum tránh được lỗi khi cố gắng tạo một hệ thống tệp đã được tạo, nhưng nếu bạn cố gắng sử dụng trả lại, Pathbạn sẽ nhận được a ClosedFileSystemException. Phản hồi @Holger hoạt động tốt cho tôi.
Jose Andias

Tôi sẽ không đóng FileSystem. Nếu bạn tải một tài nguyên từ Jar, và sau đó bạn tạo yêu cầu FileSystem- FileSystemthì cũng sẽ cho phép bạn tải các tài nguyên khác từ cùng một Jar. Ngoài ra, một khi bạn đã tạo cái mới, FileSystembạn có thể thử tải lại tài nguyên bằng cách sử dụng Paths.get(Path)và việc triển khai sẽ tự động sử dụng cái mới FileSystem.
NS du Toit

Tức là bạn không phải sử dụng #getPath(String)phương thức trên FileSystemđối tượng.
NS du Toit

5

Tôi đã viết một phương thức trợ giúp nhỏ để đọc Pathstừ tài nguyên lớp của bạn. Nó khá thuận tiện để sử dụng vì nó chỉ cần một tham chiếu của lớp mà bạn đã lưu trữ tài nguyên của mình cũng như tên của chính tài nguyên đó.

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

1

Bạn không thể tạo URI từ các tài nguyên bên trong tệp jar. Bạn có thể chỉ cần viết nó vào tệp tạm thời và sau đó sử dụng nó (java8):

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

1

Đọc tệp từ thư mục tài nguyên bằng NIO, trong 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();
    }

0

Bạn cần xác định Hệ thống tệp để đọc tài nguyên từ tệp jar như được đề cập trong https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html . Tôi thành công để đọc tài nguyên từ tệp jar với các mã dưới đây:

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)) {
            ....
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.