Чтение файла ресурса изнутри jar


243

Я хотел бы прочитать ресурс из моей банки, например, так:

File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferredReader reader = new BufferedReader(new FileReader(file));

//Read the file

и он отлично работает при запуске его в Eclipse, но если я экспортирую его в банку, то при запуске возникает исключение IllegalArgumentException:

Exception in thread "Thread-2"
java.lang.IllegalArgumentException: URI is not hierarchical

и я действительно не знаю почему, но с некоторым тестированием я обнаружил, если я изменюсь

file = new File(getClass().getResource("/file.txt").toURI());

в

file = new File(getClass().getResource("/folder/file.txt").toURI());

тогда это работает наоборот (это работает в банке, но не затмение).

Я использую Eclipse, и папка с моим файлом находится в папке класса.


Если вы хотите прочитать файлы из каталога в jar с любыми номерами для файлов, см. Stackoverflow-Link
Майкл Хегнер,

1
Я не уверен, что первоначальный вопрос касался весны. Ссылка в предыдущем комментарии ссылается на специфический для Spring ответ на другой вопрос. Я считаю, что getResourceAsStreamэто все еще более простое и более портативное решение проблемы.
Дрю Макиннис

Ответы:


398

Вместо того, чтобы пытаться обратиться к ресурсу как к файлу, просто попросите ClassLoader вернуть InputStream для ресурса вместо этого через getResourceAsStream :

InputStream in = getClass().getResourceAsStream("/file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

Пока file.txtресурс доступен в пути к классам, этот подход будет работать одинаково независимо от того, находится file.txtресурс в classes/каталоге или внутри jar.

URI is not hierarchicalПроисходит потому , что URI для ресурса внутри файла банки будет выглядеть примерно так: file:/example.jar!/file.txt. Вы не можете прочитать записи в jar( zipфайле), как если бы это был простой старый файл .

Это хорошо объясняется ответами на:


3
Спасибо, это было очень полезно, и код работает отлично, но у меня есть одна проблема, мне нужно определить, InputStreamсуществует ли (например File.exists()), чтобы моя игра могла определить, использовать ли файл по умолчанию или нет. Спасибо.
PrinceCJC

1
Да, и кстати, причина getClass().getResource("**/folder**/file.txt")заставила его работать, потому что у меня была эта папка в той же директории, что и мой jar :).
PrinceCJC

5
getResourceAsStreamвозвращает ноль, если ресурс не существует, так что это может быть вашим тестом «существует».
Дрю Макиннис

1
Кстати, у вас есть опечатка: это должен быть BufferedReader, а не BufferredReader (обратите внимание на дополнительный 'r' в более позднем)
mailmindlin

2
И конечно ... не забудьте закрыть inputStream и BufferedReader
Noremac

26

Для доступа к файлу в банке у вас есть два варианта:

  • Поместите файл в структуру каталогов, соответствующую имени вашего пакета (после извлечения файла .jar он должен находиться в том же каталоге, что и файл .class), а затем получите к нему доступ, используя getClass().getResourceAsStream("file.txt")

  • Поместите файл в корень (после извлечения файла .jar он должен быть в корне), а затем получите к нему доступ, используя Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")

Первый вариант может не работать, когда jar используется как плагин.


Большое спасибо за этот замечательный ответ ... Еще одно преимущество второго варианта заключается в том, что он также работает из метода main, потому что getClass () работает только из экземпляра, а не из статических методов.
Дэн Ортега

6

У меня была эта проблема раньше, и я сделал запасной способ загрузки. Первый способ работает в файле .jar, а второй - в Eclipse или другой IDE.

public class MyClass {

    public static InputStream accessFile() {
        String resource = "my-file-located-in-resources.txt";

        // this is the path within the jar file
        InputStream input = MyClass.class.getResourceAsStream("/resources/" + resource);
        if (input == null) {
            // this is how we load file within editor (eg eclipse)
            input = MyClass.class.getClassLoader().getResourceAsStream(resource);
        }

        return input;
    }
}

3

До сих пор (декабрь 2017 года) это единственное найденное мной решение, которое работает как внутри, так и за пределами IDE.

Используйте PathMatchingResourcePatternResolver

Примечание: это работает также в весенней загрузке

В этом примере я читаю некоторые файлы, расположенные в src / main / resources / my_folder :

try {
    // Get all the files under this inner resource folder: my_folder
    String scannedPackage = "my_folder/*";
    PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
    Resource[] resources = scanner.getResources(scannedPackage);

    if (resources == null || resources.length == 0)
        log.warn("Warning: could not find any resources in this scanned package: " + scannedPackage);
    else {
        for (Resource resource : resources) {
            log.info(resource.getFilename());
            // Read the file content (I used BufferedReader, but there are other solutions for that):
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                // ...
                // ...                      
            }
            bufferedReader.close();
        }
    }
} catch (Exception e) {
    throw new Exception("Failed to read the resources folder: " + e.getMessage(), e);
}

3

Проблема заключается в том, что некоторым сторонним библиотекам требуются имена файлов, а не входные потоки. Большинство ответов не решают эту проблему.

В этом случае одним из обходных путей является копирование содержимого ресурса во временный файл. В следующем примере используется jUnit TemporaryFolder.

    private List<String> decomposePath(String path){
        List<String> reversed = Lists.newArrayList();
        File currFile = new File(path);
        while(currFile != null){
            reversed.add(currFile.getName());
            currFile = currFile.getParentFile();
        }
        return Lists.reverse(reversed);
    }

    private String writeResourceToFile(String resourceName) throws IOException {
        ClassLoader loader = getClass().getClassLoader();
        InputStream configStream = loader.getResourceAsStream(resourceName);
        List<String> pathComponents = decomposePath(resourceName);
        folder.newFolder(pathComponents.subList(0, pathComponents.size() - 1).toArray(new String[0]));
        File tmpFile = folder.newFile(resourceName);
        Files.copy(configStream, tmpFile.toPath(), REPLACE_EXISTING);
        return tmpFile.getAbsolutePath();
    }

Это затуманено много. Спасибо, что задокументировали это. Мне интересно, какие еще варианты существуют без JUnit.
Алекс Мур-Ниеми

0

Если вы хотите прочитать как файл, я думаю, что есть еще похожее решение:

    ClassLoader classLoader = getClass().getClassLoader();
    File file = new File(classLoader.getResource("file/test.xml").getFile());

4
URL.getFile () не преобразует URL в имя файла. Он возвращает часть URL-адреса после хоста с сохранением всех процентных кодировок, поэтому, если путь содержит какие-либо не-ASCII-символы или любые ASCII-символы, недопустимые в URL-адресах (включая пробелы), результатом будет не существующее имя файла , даже если URL является file:URL.
VGR

48
это не работает внутри, как только программа
Акшай Касар

1
Не работает из jar, если вы не конвертируете в строку и не сохраняете ее сначала локально.
smoosh911

0

Убедитесь, что вы работаете с правильным разделителем. Я заменил все /в относительном пути с File.separator. Это отлично работало в IDE, однако не работало в JAR сборки.


0

Я думаю, что это должно работать и в Java. Следующий код, который я использую, использует kotlin.

val resource = Thread.currentThread().contextClassLoader.getResource('resources.txt')

0

Я нашел исправление

BufferedReader br = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream(path)));

Замените «Main» на класс java, в котором вы его закодировали. Замените «path» на путь в файле jar.

например, если вы поместите State1.txt в пакет com.issac.state, введите путь как «/ com / issac / state / State1», если вы используете Linux или Mac. Если вы используете Windows, введите путь как «\ com \ issac \ state \ State1». Не добавляйте расширение .txt в файл, если не возникает исключение «Файл не найден».


-1

Вы можете использовать загрузчик классов, который будет читать из classpath как путь ROOT (без "/" в начале)

InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

-1

Если вы используете Spring, то вы можете использовать следующий метод для чтения файла из src / main / resources:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.springframework.core.io.ClassPathResource;

  public String readFileToString(String path) throws IOException {

    StringBuilder resultBuilder = new StringBuilder("");
    ClassPathResource resource = new ClassPathResource(path);

    try (
        InputStream inputStream = resource.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {

      String line;

      while ((line = bufferedReader.readLine()) != null) {
        resultBuilder.append(line);
      }

    }

    return resultBuilder.toString();
  }

1
Добро пожаловать в ТАК. Это не дает ответа на вопрос. Как только у вас будет достаточно репутации, вы сможете комментировать любой пост. Также проверьте это, что я могу сделать вместо этого .
thewaywewere

Это удалит разрывы строк в файле!
elad.chen

-1

По какой-то причине classLoader.getResource()всегда возвращал null, когда я развертывал веб-приложение в WildFly 14. Получение classLoader из getClass().getClassLoader()или Thread.currentThread().getContextClassLoader()возвращает null.

getClass().getClassLoader() Документ API говорит,

«Возвращает загрузчик класса для класса. Некоторые реализации могут использовать null для представления загрузчика класса начальной загрузки. Этот метод будет возвращать null в таких реализациях, если этот класс был загружен загрузчиком класса начальной загрузки».

может быть, если вы используете WildFly и ваше веб-приложение попробуйте это

request.getServletContext().getResource()вернул URL ресурса. Здесь запрос является объектом ServletRequest.


-2

Ниже приведен код, работающий с Spring boot (kotlin):

val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.