从 jar 中读取资源文件

Reading a resource file from within jar

提问人:Koi 提问时间:12/5/2013 最后编辑:NM NaufaldoKoi 更新时间:9/5/2022 访问量:482195

问:

我想从我的罐子里读取一个资源,如下所示:

File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferedReader 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,并且包含我的文件的文件夹位于 class 文件夹中。

java 文件 jar 资源 embedded-resource

评论

0赞 Michael Hegner 10/3/2016
如果要从 jar 中的目录中读取文件,其中包含文件的任何数字,请参阅 Stackoverflow-Link
1赞 Drew MacInnis 12/15/2016
我不确定最初的问题是否涉及 Spring。上一条评论中的链接引用了来自不同问题的 Spring 特定答案。我相信仍然是一个更简单、更便携的问题解决方案。getResourceAsStream

答:

525赞 Drew MacInnis 12/5/2013 #1

与其尝试将资源作为文件进行寻址,不如让 ClassLoader 返回资源的 InputStream,而是通过 getResourceAsStream

try (InputStream in = getClass().getResourceAsStream("/file.txt");
    BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
    // Use resource
}

只要资源在类路径上可用,那么无论资源是在目录中还是在目录中,此方法都将以相同的方式工作。file.txtfile.txtclasses/jar

发生这种情况是因为 jar 文件中资源的 URI 将如下所示:.您不能像读取普通旧文件一样读取(文件)中的条目。URI is not hierarchicalfile:/example.jar!/file.txtjarzip

以下答案很好地解释了这一点:

评论

3赞 Koi 12/5/2013
谢谢,这很有帮助,代码运行良好,但我确实有一个问题,我需要确定是否存在(如),以便我的游戏可以判断是否使用默认文件。 谢谢。InputStreamFile.exists()
1赞 Koi 12/5/2013
哦,顺便说一句,让它工作的原因是因为我将该文件夹与我的罐子:)放在同一目录中。getClass().getResource("**/folder**/file.txt")
10赞 Drew MacInnis 12/6/2013
getResourceAsStream如果资源不存在,则返回 null,以便可以作为“存在”测试。
2赞 Noremac 5/15/2015
当然......不要忘记关闭 inputStream 和 BufferedReader
5赞 ptkvsk 1/9/2020
You cannot read the entries within a jar (a zip file) like it was a plain old File.这很糟糕,因为有大量的库函数需要文件路径作为输入。
-3赞 pablo.vix 11/17/2014 #2

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

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

评论

5赞 VGR 3/6/2016
URL.getFile() 不会将 URL 转换为文件名。它返回主机之后的 URL 部分,并且所有百分比编码都保持不变,因此,如果路径包含任何非 ASCII 字符或 URL 中不允许的任何 ASCII 字符(包括空格),则结果将不是现有文件名,即使 URL 是 URL。file:
52赞 Akshay Kasar 9/20/2017
一旦程序构建到 jar 中,这在内部就不起作用了
1赞 smoosh911 8/10/2018
除非您转换为字符串并先将其保存在本地,否则无法从 jar 中工作。
49赞 Juozas Kontvainis 9/17/2016 #3

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

  • 将文件放在与您的包名称匹配的目录结构中(提取 .jar 文件后,它应该与 .class 文件位于同一目录中),然后使用getClass().getResourceAsStream("file.txt")

  • 将文件放在根目录下(提取 .jar 文件后,它应该在根目录下),然后使用Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")

当 jar 用作插件时,第一个选项可能不起作用。

评论

0赞 Dan Ortega 2/6/2020
非常感谢你给出这个很好的答案......第二个选项的另一个好处是它也可以从 main 方法工作,因为 getClass() 只能从实例工作,而不能从静态方法工作。
1赞 Petterson 2/17/2017 #4

确保使用正确的分隔符。我用 .这在 IDE 中工作正常,但在构建 JAR 中不起作用。/File.separator

-2赞 Sujan M. 5/3/2017 #5

如果你使用的是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 5/3/2017
欢迎来到 SO。这并不能提供问题的答案。一旦您有足够的声誉,您将能够对任何帖子发表评论。还要检查一下,我能做些什么
0赞 elad.chen 3/3/2019
这将删除文件中的换行符!
12赞 MForm 6/27/2017 #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;
    }
}

评论

0赞 divyanayan awasthi 3/22/2021
对我不起作用....我在 jar 中的资源中有 .wsdl ...但它仍然无法解决 WSDL
8赞 Naor Bar 12/20/2017 #7

到目前为止(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);
}
-1赞 sendon1982 9/14/2018 #8

您可以使用类加载器,它将从类路径读取为 ROOT 路径(开头不带“/”)

InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
-1赞 Ram C 3/1/2019 #9

出于某种原因,当我将 Web 应用程序部署到 WildFly 14 时,总是返回 null。获取 classLoader from 或返回 null。classLoader.getResource()getClass().getClassLoader()Thread.currentThread().getContextClassLoader()

getClass().getClassLoader()API doc 说道,

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

如果您使用的是 WildFly 和您的 Web 应用程序,请尝试一下

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

7赞 Navneeth 3/7/2019 #10

问题在于某些第三方库需要文件路径名而不是输入流。大多数答案都没有解决这个问题。

在这种情况下,一种解决方法是将资源内容复制到临时文件中。以下示例使用 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();
    }

评论

0赞 Alex Moore-Niemi 3/10/2020
这被掩盖了很多。感谢您记录它。我很好奇没有 JUnit 还有什么其他选择。
3赞 Holger 8/26/2020
由于 应该始终用作分隔符,不像里面的操作,它使用系统特定的文件分隔符,与 相比,该方法不仅不必要地复杂,甚至不正确。此外,目前尚不清楚为什么不使用其结果时调用。resourceName/FiledecomposePathpath.split("/").toArray(new String[0]))
1赞 ElectRocnic 10/1/2020
什么是,来自哪个库,什么是......?请在您的帖子中提供完整的信息。folderListsREPLACE_EXISTING
-2赞 Bikash Das 5/8/2019 #11

以下代码适用于 Spring boot(kotlin):

val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))
1赞 user13072113 3/17/2020 #12

我找到了解决方法

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

将“Main”替换为您编写代码的 java 类。将“path”替换为 jar 文件中的路径。

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

0赞 Valentino Ricci 8/28/2020 #13

此代码适用于 Eclipse 和 Exported Runnable JAR

private String writeResourceToFile(String resourceName) throws IOException {
    File outFile = new File(certPath + File.separator + resourceName);

    if (outFile.isFile())
        return outFile.getAbsolutePath();
    
    InputStream resourceStream = null;
    
    // Java: In caso di JAR dentro il JAR applicativo 
    URLClassLoader urlClassLoader = (URLClassLoader)Cypher.class.getClassLoader();
    URL url = urlClassLoader.findResource(resourceName);
    if (url != null) {
        URLConnection conn = url.openConnection();
        if (conn != null) {
            resourceStream = conn.getInputStream();
        }
    }
    
    if (resourceStream != null) {
        Files.copy(resourceStream, outFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        return outFile.getAbsolutePath();
    } else {
        System.out.println("Embedded Resource " + resourceName + " not found.");
    }
    
    return "";
}   

评论

2赞 Tom Taylor 11/18/2020
这里有什么?certPath
4赞 user9869932 9/10/2020 #14

就我而言,我终于做到了

import java.lang.Thread;
import java.io.BufferedReader;
import java.io.InputStreamReader;

final BufferedReader in = new BufferedReader(new InputStreamReader(
      Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt"))
); // no initial slash in file.txt

评论

1赞 ElectRocnic 10/2/2020
奇怪的是,这也是我使用嵌套资源的解决方案。我想知道为什么它似乎适用于其他发布的答案中的斜杠。
1赞 Srishti Sharma 10/7/2020
@ElectRocnic 我认为这是因为路径是如何解释的。“/file.txt”说,切换到当前目录并打开文件.txt,而“文件.txt”说,从当前位置打开文件.txt。它们都通向同一个位置。对我来说,两者都可以在 Windows 系统中工作。
0赞 ElectRocnic 10/7/2020
啊哈,它在 docker+linux 环境中对我不起作用......谢谢你的发言
0赞 Yaniv K. 11/18/2021
只是为我工作getClass().getClassLoader().getResourceAsStream()
0赞 Mahdi 8/21/2021 #15

最后我解决了错误:

String input_path = "resources\\file.txt";
        
        input_path = input_path.replace("\\", "/");  // doesn't work with back slash
        
        URL file_url = getClass().getClassLoader().getResource(input_path);
        String file_path = new URI(file_url.toString().replace(" ","%20")).getSchemeSpecificPart();
        InputStream file_inputStream = file_url.openStream();

评论

0赞 Jaco-Ben Vosloo 8/21/2021
请考虑添加有关解决方案及其工作原理的更多信息