正在检索子包的资源 class

Retrieving resources for a subpackage class

我使用 java 的时间不长,所以我不确定还需要寻找什么。我希望有人能给我指出正确的方向。

目标: 我想使用查找 table,存储为文本文件。但我不想使用绝对路径,因为最后我想打包一个版本并能够从任何位置调用它(文本文件将包含在打包版本中)。

当前设置: 我将文本文件放在一个名为 "resources" 的文件夹中(因为通过阅读有关 java 的教程,我得到的印象是,我应该把它放在这里以维护结构更好的项目)。

在根包文件夹中,我有一个 class (MainClass.java) 在子包中调用另一个 class (LookUpClass.java)。 文件夹设置如下:

我在 LookUpClass.java 中写了一个方法,它从我在资源中的查找 table 中检索某一行。为了检索文件并读出某一行,我使用了

// Gets respective line from LUT
private static String getLineFromLUT(int line) {
    URL url = LookUpClass.class.getClass().getResource("/LookUpTables/LookUpTable1.txt");
    File file = new File(url.toURI());
    BufferedReader br = new BufferedReader(new FileReader(file));

    for (int i = 0; i < line; ++i)
        br.readLine();

    return br.readLine;
}

在我的项目结构中,"java" 文件夹被标记为 "source",而 "resources" 被标记为 "resources"。

我的测试设置非常简单:

public static void main(String[] args) throws URISyntaxException, IOException {
    String c = LookUpClass.getLineFromLUT(5);
    System.out.println("Color of line 5: " + c);
}

输出:

Color of line 5: 0  0   38

(正确。)

我向 PlotterClass.java 添加了完全相同的行,它也工作正常。

问题:

现在,如果我在 MainClass.java 中尝试相同的操作,我会收到 url 为空的错误。似乎找不到 resource/resource 文件夹。

我已经阅读了关于 SO 的各种帖子并尝试了几个建议的解决方案,但到目前为止都失败了:

我不知道如何解决这个问题。我意识到在这一点上我只是在尝试一些东西,甚至不明白为什么它可以或不能工作。 对我来说,似乎 LookUpClass.javaMainClass.java 来自不同的位置 运行。但是 "resources" 文件夹和相应的文本文件位置永远不会改变。怎么会一种情况下找到文件,另一种情况下找不到?

根据您提供的 MWE,我尝试重现您的问题,但没有成功。我在此处上传了我的项目,其中包括 pom.xml(您提到您使用了 maven):http://www.filedropper.com/Whosebug 这是我的查找 class 的样子(还展示了如何使用 getResourceAsStream 方法):

public class LookUpClass {

    final static String tableName = "resources/LookUpTables/LookUpTable1.txt";

    public static String getLineFromLUT(final int line) {
        final URL url = LookUpClass.class.getResource(tableName);
        if (url.toString().startsWith("jar:")) {
            try (final URLClassLoader loader = URLClassLoader
                    .newInstance(new URL[] { url })) {
                return getLineFromLUT(
                        new InputStreamReader(
                                loader.getResourceAsStream(tableName)), line);
            } catch (final IOException e) {
                e.printStackTrace();
            }
        } else {
            return getLineFromLUT(
                    new InputStreamReader(
                            LookUpClass.class.getResourceAsStream(tableName)),
                    line);
        }
        return null;
    }

    public static String getLineFromLUT(final Reader reader, final int line) {
        try (final BufferedReader br = new BufferedReader(reader)) {
            for (int i = 0; i < line; ++i)
                br.readLine();
            return br.readLine();
        } catch (final IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Maven 有一个 standard directory layout。目录 src/main/resources 用于此类应用程序资源。将您的文本文件放入其中。

您现在基本上有两个选项可以准确地放置您的文件:

  1. 资源文件属于class。

一个例子是 class 代表一个 GUI 元素(一个面板),它也需要显示一些图像。

在这种情况下,将资源文件放入与相应class相同的目录(包)中。例如。对于名为 your.pkg.YourClass 的 class 将资源文件放入目录 your/pkg:

src/main
 +-- java/
 |    +-- your/pkg/
 |    |    +-- YourClass.java
 +-- resources/
      +-- your/pkg/
           +-- resource-file.txt

您现在通过相应的 class 加载资源。在 class your.pkg.YourClass 中,您有以下用于加载的代码片段:

String resource = "resource-file.txt"; // the "file name" without any package or directory
Class<?> clazz = this.getClass(); // or YourClass.class
URL resourceUrl = clazz.getResource(resource);
if (resourceUrl != null) {
    try (InputStream input = resourceUrl.openStream()) {
        // load the resource here from the input stream
    }
}

注意:您也可以通过 class' class 加载程序加载资源:

String resource = "your/pkg/resource-file.txt";
ClassLoader loader = this.getClass().getClassLoader(); // or YourClass.class.getClassLoader()
URL resourceUrl = loader.getResource(resource);
if (resourceUrl != null) {
    try (InputStream input = resourceUrl.openStream()) {
        // load the resource here from the input stream
    }
}

选择,你觉得更方便。

  1. 资源属于应用整体。

在这种情况下,只需将资源直接放入 src/main/resources 目录或适当的子目录即可。让我们看一下您的查找文件的示例:

src/main/resources/
    +-- LookupTables/
         +-- LookUpTable1.txt

然后您必须通过 class 加载器加载资源,使用当前线程的上下文 class 加载器或应用程序 class 加载器(无论哪个更合适 - 去搜索有关此问题的文章,如果有兴趣)。我会告诉你两种方式:

String resource = "LookupTables/LookUpTable1.txt";
ClassLoader ctxLoader = Thread.currentThread().getContextClassLoader();
ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
URL resourceUrl = ctxLoader.getResource(resource); // or sysLoader.getResource(resource)
if (resourceUrl != null) {
    try (InputStream input = resourceUrl.openStream()) {
        // load the resource here from the input stream
    }
}

作为第一个建议,使用当前线程的上下文 class 加载器。在独立应用程序中,这将是系统 class 加载程序或将系统 class 加载程序作为父级。 (这些 class 加载器之间的区别对于同时加载资源的库来说很重要。)

您应该始终使用 class 加载程序来加载资源。通过这种方式,您可以独立于位置进行加载(只需注意文件在启动应用程序时位于 class 路径内)并且您可以将整个应用程序打包到一个仍然可以找到资源的 JAR 文件中。