java.nio.file.Path для ресурсу classpath


143

Чи існує API, щоб отримати ресурс classpath (наприклад, з чого я отримав Class.getResource(String)) як java.nio.file.Path? В ідеалі, я хотів би використовувати нові фантазії PathAPI з ресурсами classpath.


3
Ну, пройшовши довгий шлях (призначений каламбур), ви маєте Paths.get(URI), а потім ´URL.toURI () , and last getResource () `, який повертає a URL. Можливо, ви зможете це з'єднати. Не намагався, хоча.
NilsH

Відповіді:


174

Цей для мене працює:

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

7
@VGR, якщо ресурси у .jar-файлі можуть спробувати цей `Resource resource = new ClassPathResource (" use.txt "); BufferedReader читач = новий BufferedReader (новий InputStreamReader (resource.getInputStream ())); `см stackoverflow.com/questions/25869428 / ...
zhuguowei

8
@zhuguowei - це специфічний для весни підхід. Він взагалі не працює, коли Spring не використовується.
Райан Дж. Макдоноф

2
Якщо ваша програма не покладається на системний завантажувач, це має бутиThread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
ThrawnCA

27

Здогадуючись, що те, що ви хочете зробити, - це зателефонувати Files.lines (...) на ресурс, що надходить із classpath - можливо, зсередини банку.

Оскільки Oracle суперечив поняттю, коли Path - це Шлях, не змушуючи 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.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 або новішої версії ви можете використовувати лямбда-вирази або посилання методів для представлення фактичної дії, наприклад

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 classpaths. Це безумовно, як слід скопіювати багато ресурсів у Java 7+.
Мітчелл Скаггс

10

Виявляється, ви можете це зробити за допомогою вбудованого постачальника Zip File System . Однак передача URI ресурсу безпосередньо Paths.getне працюватиме; замість цього спочатку потрібно створити файлову систему zip для URI jar без введення імені, а потім посилатися на запис у цій файловій системі:

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, але ніколи не закриває його. Найкращим підходом є передача об'єкта, що нагадує споживача, як і відповідь Хольгера. Відкрийте файлову систему ZipFS достатньо довго, щоб працівник міг робити все, що потрібно робити із Шляхом (доки працівник не намагатиметься зберегти об'єкт Path для подальшого використання), а потім закрийте FileSystem.


11
Будьте уважні до новостворених фс. Другий виклик, що використовує той самий jar, призведе до винятку, що скаржиться на вже існуючу файлову систему. Буде краще спробувати (FileSystem fs = ...) {return fs.getPath (entryName);} або, якщо ви хочете, щоб цей кеш був зроблений, більш досконалим керуванням. У нинішній формі це ризиковано.
рейдеркостін

3
Окрім проблеми потенційно незамкненої нової файлової системи, припущення про взаємозв’язок між схемами та необхідність відкриття нової файлової системи та загадки із вмістом URI обмежують корисність рішення. Я створив нову відповідь, яка показує загальний підхід, який спрощує операцію і одночасно обробляє нові схеми, як нове сховище класу Java 9. Він також працює, коли хтось із додатка вже відкрив файлову систему (або метод викликається двічі за одну і ту ж банку)…
Holger

Залежно від використання цього рішення, незакрите newFileSystemможе призвести до безлічі ресурсів, що навішуються відкритими назавжди. Хоча додаток @raisercostin дозволяє уникнути помилки при спробі створити вже створену файлову систему, якщо ви спробуєте використати повернуті, Pathви отримаєте ClosedFileSystemException. @Holger відповідь добре працює для мене.
Хосе Андіас

Я б не закрив цю FileSystem. Якщо ви завантажуєте ресурс з Jar, а потім створюєте необхідні FileSystem- це FileSystemтакож дозволить вам завантажувати інші ресурси з того ж Jar. Крім того, щойно ви створили нове, FileSystemви можете просто спробувати завантажити ресурс знову за допомогою, Paths.get(Path)і реалізація автоматично використовуватиме новий 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

Ви не можете створити URI з ресурсів всередині файлу jar. Ви можете просто записати його у тимчасовий файл, а потім використовувати його (java8):

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

1

Прочитайте файл з папки ресурсів за допомогою NIO, в 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

Вам потрібно визначити Filesystem, щоб читати ресурс з файлу 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)) {
            ....
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.