从jar中读取资源文件


240

我想像这样从我的jar中读取资源:

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

//Read the file

并且在Eclipse中运行它时效果很好,但是如果我将其导出到jar中运行,则会出现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());

然后它的作用相反(它在jar中起作用,但在Eclipse中不起作用)。

我正在使用Eclipse,并且包含我的文件的文件夹位于类文件夹中。


如果要从jar中的目录中读取文件,且其中包含文件的任何编号,请参阅Stackoverflow-Link
Michael Hegner,2016年

1
我不确定最初的问题是否涉及Spring。上一条注释中的链接引用了来自其他问题的特定于Spring的答案。我相信getResourceAsStream仍然是解决该问题的更简单,更便携的解决方案。
Drew MacInnis

Answers:


395

而不是尝试将资源作为文件来寻址,只需让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发生这种情况是因为jar文件中资源的URI看起来像这样:file:/example.jar!/file.txt。您无法读取jarzip文件)中的条目,就像原来的旧文件一样

可以通过以下答案很好地解释这一点:


3
谢谢您,这非常有帮助,并且代码可以完美地工作,但是我确实有一个问题,我需要确定是否InputStream存在(例如File.exists()),以便我的游戏可以判断是否使用默认文件。谢谢。
2013年

1
哦,顺便说一句,之所以getClass().getResource("**/folder**/file.txt")起作用,是因为我将该文件夹与jar放在同一目录中:)。
PrinceCJC 2013年

5
getResourceAsStream如果资源不存在,则返回null,以便可以进行“存在”测试。
德鲁·麦金尼斯

1
顺便说一句,您有错字:应该是BufferedReader,而不是BufferredReader(请注意后面的多余“ r”)
mailmindlin 2014年

2
当然,...别忘了关闭inputStream和BufferedReader
Noremac

26

要访问jar中的文件,您有两个选择:

  • 将文件放在与包名称匹配的目录结构中(提取.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年12月),这是我发现的唯一在IDE内外均可使用的解决方案。

使用PathMatchingResourcePatternResolver

注意:它在spring-boot中也可以使用

在此示例中,我正在读取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还有哪些其他选择。
Alex Moore-Niemi

0

如果您想阅读为文件,我相信仍然有一个类似的解决方案:

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

4
URL.getFile()将URL转换为文件名。它返回主机之后的URL部分,所有百分比编码均保持不变,因此,如果路径包含任何非ASCII字符或URL中不允许的任何ASCII字符(包括空格),则结果将不是现有的文件名,即使该URL是file:URL。
VGR

48
一旦将程序构建到jar中,这将无法在内部运行
Akshay Kasar

1
除非您转换为字符串并将其首先保存在本地,否则不能从jar中使用。
smoosh911 '18

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

用您在其中编码的Java类替换“ Main”。用jar文件中的路径替换“ path”。

例如,如果将State1.txt放入com.issac.state包中,则在运行Linux或Mac的情况下,将路径键入为“ / com / issac / state / State1”。如果运行Windows,则将路径键入为“ \ com \ issac \ state \ State1”。除非发生“找不到文件”异常,否则请勿将.txt扩展名添加到文件中。


-1

您可以使用类加载器,该类加载器将从类路径读取为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
欢迎来到SO。这不能为问题提供答案。拥有足够的声誉后,您就可以在任何帖子中发表评论。还要检查一下该怎么办
thewaywere是

这将删除文件中的换行符!
elad.chen

-1

出于某种原因,classLoader.getResource()始终返回null,当我部署的Web应用程序WildFly 14.从得到的类加载器getClass().getClassLoader()Thread.currentThread().getContextClassLoader()返回null。

getClass().getClassLoader() API文件说,

“返回该类的类加载器。某些实现可能使用null来表示引导类加载器。如果此类是由引导类加载器加载的,则此方法在此类实现中将返回null。”

如果您使用的是WildFly,并且您的网络应用程序可能会尝试此操作

request.getServletContext().getResource()返回资源URL。这里的request是ServletRequest的对象。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.