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.Path
không? Lý tưởng nhất là tôi muốn sử dụng các Path
API mới lạ mắt với tài nguyên đường dẫn.
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.Path
không? Lý tưởng nhất là tôi muốn sử dụng các Path
API mới lạ mắt với tài nguyên đường dẫn.
Câu trả lời:
Cái này hoạt động với tôi:
return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
Đ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();
class.getResource
yêu cầu một dấu gạch chéo nhưng getSystemResourceAsStream
không thể tìm thấy tệp khi được thêm tiền tố bằng dấu gạch chéo.
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 file
URI 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.lang
trong ví dụ) dưới dạng Path
s, 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.class
cho 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ữ.
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.get
khô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.
newFileSystem
có 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, Path
bạn sẽ nhận được a ClosedFileSystemException
. Phản hồi @Holger hoạt động tốt cho tôi.
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
- FileSystem
thì 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, FileSystem
bạ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
.
#getPath(String)
phương thức trên FileSystem
đối tượng.
Tôi đã viết một phương thức trợ giúp nhỏ để đọc Paths
từ 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());
}
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);
Đọ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();
}
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)) {
....
}
}
Paths.get(URI)
, sau đó là hungURL.toURI (), and last
getResource () `trả về aURL
. Bạn có thể xâu chuỗi chúng lại với nhau. Haven Xnt đã cố gắng mặc dù.