getResourceAsStream返回空值

259

我正在从Java项目的编译JAR包中的一个包中加载文本文件。相关的目录结构如下:

/src/initialization/Lifepaths.txt

我的代码通过调用Class::getResourceAsStream来加载文件,返回一个InputStream

public class Lifepaths {
    public static void execute() {
        System.out.println(Lifepaths.class.getClass().
            getResourceAsStream("/initialization/Lifepaths.txt"));
    }

    private Lifepaths() {}

    //This is temporary; will eventually be called from outside
    public static void main(String[] args) {execute();}
}

无论我使用什么,打印输出始终会打印null。我不确定为什么上面的方法行不通,所以我也尝试了以下方法:

  • "/src/initialization/Lifepaths.txt"
  • "initialization/Lifepaths.txt"
  • "Lifepaths.txt"

这些都不起作用。 查阅了 很多 相关问题,但它们都没有帮助——通常,它们只是说要使用根路径加载文件,而我已经在这样做了。或者从当前目录加载文件(只需加载filename),我也尝试过了。该文件正在适当的位置以适当的名称编译成JAR文件。

我该如何解决这个问题?


5
你确定已经检查过它确实在jar文件中吗?你检查过文件的大小写了吗? - Jon Skeet
1
@JonSkeet 它确实被编译成了位于适当位置的 JAR 文件中,并且大小写也是正确的。 - user1131435
1
@greedybuddha 虽然我无法从静态上下文中调用它,但我可以使用 Lifepaths.class 来调用它。话虽如此,为什么 getClassLoader() 允许它工作呢?(另外,随意发布一个答案!) - user1131435
你能展示一下 Lifepaths.getClass() 吗?在 Object 中没有定义这样的静态方法... - Puce
1
看一下这个答案,看看你是否可以使用getResource(String)使其正常工作。顺便说一句 - 我总是在static上下文中遇到问题。问题基本上是获得的类加载器是用于J2SE类的类加载器。您需要访问上下文类加载器,该类加载器适用于应用程序本身。 - Andrew Thompson
显示剩余4条评论
27个回答

227
Lifepaths.class.getClass().getResourceAsStream(...)使用系统类加载器加载资源,显然会失败,因为它无法看到您的JAR文件。
Lifepaths.class.getResourceAsStream(...)使用加载Lifepaths类的相同类加载器加载资源,应该可以访问您的JAR文件中的资源。
在调用getResourceAsStream(name)时,名称必须以"/"开头。我不确定这是否是必需的,但如果没有它,我会遇到问题。

207
补充一下,当调用getResourceAsStream(name)方法时,name参数必须以"/"开头。我不确定这是否必要,但如果没有它,我会遇到问题。 - David
5
我从早晨八点开始就在摆弄这个问题,直到现在。它帮了我大忙。我还需要添加一个前导斜杠才能使其正常工作。 - kyle
4
请注意,所需资源可能在包层次结构之外。在这种情况下,您需要在路径中使用"../"以向上一级并向下到另一个路径分支来获取您的资源。 - Zon
5
@David -- 我认为这个斜杠(leading '/')是必要的,否则它会相对于Lifepaths.class包进行搜索。 - Mz A
17
只需要补充一些信息,如果您的文件位于不同目录下,则需要在路径之前添加 /,例如 initialization/Lifepaths.txt。 如果文件的路径与您的类相同(但是在资源文件夹下),则只需将文件名放入即可,无需加上任何 /。 例如,如果您的类具有以下路径 src/main/java/paths/Lifepaths.java,则您的文件必须具有以下路径 src/main/resources/paths/Lifepaths.txt - Dwhitz
显示剩余6条评论

70

规则如下:

  1. 检查您想要在JAR文件中加载的文件位置(并确保它实际上已添加到JAR中)
  2. 使用绝对路径:路径从JAR的根目录开始
  3. 使用相对路径:路径从调用getResource/getResourceAsStream的类的包目录开始

尝试一下:

Lifepaths.class.getResourceAsStream("/initialization/Lifepaths.txt")

取代

Lifepaths.class.getClass().getResourceAsStream("/initialization/Lifepaths.txt")

我不确定是否有区别,但前者将使用正确的ClassLoader/JAR,而对于后者我不确定。


2
我已经完成了这三件事情。请重新阅读我的问题。 - user1131435
从你的问题中并不清楚“相关目录结构”是什么,以及你是否已经检查了文件在JAR包中的位置(步骤1)。 - Puce
1
你关于相对路径的评论最终解决了我的问题,谢谢! - Paul Bormans
有趣的是,原来我必须使用斜杠“/config.properties”才能访问它... - Erk

60

从jar获取资源有几种方法,每种方法的语法略有不同,需要指定不同的路径。

我看过的最好的解释是来自InfoWorld的这篇文章。我会在这里做一个概括,但如果您想了解更多信息,应该查看文章。

方法

  1. ClassLoader.getResourceAsStream()

格式:使用“/”分隔的名称;没有前导“/”(所有名称都是绝对的)。

示例:this.getClass().getClassLoader().getResourceAsStream("some/pkg/resource.properties");

  1. Class.getResourceAsStream()

格式:使用“/”分隔的名称;前导“/”表示绝对名称;所有其他名称都相对于类的包

示例:this.getClass().getResourceAsStream("/some/pkg/resource.properties");

更新于2020年9月:更改文章链接。原始文章来自Javaworld,现在托管在InfoWorld上(并且有更多广告)


你的第二个例子有问题。 - Janus Troelsen
1
第二个例子并没有出错,正如我在答案中所说,这取决于资源的位置。 - greedybuddha
请确保你的IDE通过刷新源文件夹来识别文件('some/pkg/resource.properties')。 - Eric Duminil
JavaWorld的链接已经失效。 - Dominic Cronin
如果有帮助的话,对我来说根目录是/target/classes。因此,如果我使用方法一加载文件“test.json”,则编译后的代码或相应的jar文件中需要将文件放在/target/classes/test.json位置。 - alianos-
非常感谢。这是第一个最终教会我(非常重要的)区别的答案。 - Father Stack

19

粗略地讲:

getClass().getResource("/")Thread.currentThread().getContextClassLoader().getResource(".")

假设您的项目结构如下:

├── src
│   ├── main
│   └── test
│       ├── java
│       │   └── com
│       │       └── github
│       │           └── xyz
│       │               └── proj
│       │                   ├── MainTest.java
│       │                   └── TestBase.java
│       └── resources
│           └── abcd.txt
└── target
    └── test-classes  <-- this.getClass.getResource("/")
        │              `--Thread.currentThread().getContextClassLoader().getResources(".")
        ├── com
        │   └── github
        │       └── xyz
        │           └── proj  <-- this.getClass.getResource(".")
        │               ├── MainTest.class
        │               └── TestBase.class
        └── resources
            └── abcd.txt

// in MainTest.java
this.getClass.getResource("/") -> "~/proj_dir/target/test-classes/"
this.getClass.getResource(".") -> "~/proj_dir/target/test-classes/com/github/xyz/proj/"
Thread.currentThread().getContextClassLoader().getResources(".") -> "~/proj_dir/target/test-classes/"
Thread.currentThread().getContextClassLoader().getResources("/") ->  null


1
这个解释对我来说澄清了很多问题。谢谢! - João Victor Oliveira
1
从未意识到 Class 的 getResource 方法与 ClassLoader 的 getResources 方法没有相同的根。这个答案应该在页面顶部。 - user327961
1
救了我的命!!!视觉化的答案是最好的部分。 - testing_22

15

不要使用绝对路径,让它们相对于您项目中的“resources”目录。 显示来自目录“资源”中MyTest.txt内容的快速而简单的代码。

@Test
public void testDefaultResource() {
    // can we see default resources
    BufferedInputStream result = (BufferedInputStream) 
         Config.class.getClassLoader().getResourceAsStream("MyTest.txt");
    byte [] b = new byte[256];
    int val = 0;
    String txt = null;
    do {
        try {
            val = result.read(b);
            if (val > 0) {
                txt += new String(b, 0, val);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } 
    } while (val > -1);
    System.out.println(txt);
}

10
我发现自己面临了类似的问题。由于我使用的是maven,所以我需要更新我的pom.xml文件,加入以下内容:
   ...
</dependencies>
<build>
    <resources>
        <resource>
            <directory>/src/main/resources</directory>
        </resource>
        <resource>
            <directory>../src/main/resources</directory>
        </resource>
    </resources>
    <pluginManagement>
        ...

请注意其中的资源标签,以指定该文件夹的位置。如果您有嵌套项目(就像我一样),则可能希望从其他区域获取资源,而不仅仅是在您正在工作的模块中。这有助于减少在每个代码库中保留相同文件的工作量,特别是当您使用类似的配置数据时。

9

3
这正是getResourceAsStream()所做的。 - fedeb

8

你使用的ClassLoader似乎存在问题。请使用contextClassLoader来加载类,无论它是在静态或非静态方法中。

Thread.currentThread().getContextClassLoader().getResourceAsStream......


5

默认的JVM类加载器将首先使用父类加载器来加载资源:deletegate-parent-classloader

Lifepaths.class.getClass()的类加载器是引导类加载器,因此getResourceAsStream将仅搜索$JAVA_HOME,无论用户提供的classpath如何。显然,Lifepaths.txt不在那里。

Lifepaths.class的类加载器是系统类路径类加载器,因此getResourceAsStream将搜索用户定义的classpath,而Lifepaths.txt就在那里。

在使用java.lang.Class#getResourceAsStream(String name)时,以'/'开头的名称将添加包名作为前缀。要避免这种情况,请改用java.lang.ClassLoader#getResourceAsStream

例如:

ClassLoader loader = Thread.currentThread().getContextClassLoader();
String resourceName = "Lifepaths.txt";
InputStream resourceStream = loader.getResourceAsStream(resourceName); 

4
我成功的方法是将文件添加到 My Project/Java Resources/src 目录下,然后使用。
this.getClass().getClassLoader().getResourceAsStream("myfile.txt");

我不需要明确地将此文件添加到路径中(将其添加到/src中似乎可以实现这一点)


错误的Java语法。 - Ilya Kharlamov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接