如何获取Java JAR文件中资源的路径

186

我正在尝试获取资源的路径,但是一直没有成功。

这种方式可以工作(在IDE和JAR中都可以),但是这样我只能获取文件内容而无法获取文件路径:

ClassLoader classLoader = getClass().getClassLoader();
PrintInputStream(classLoader.getResourceAsStream("config/netclient.p"));

如果我这样做:

ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("config/netclient.p").getFile());

结果是: java.io.FileNotFoundException: file:/path/to/jarfile/bot.jar!/config/netclient.p (无此文件或目录)

是否有办法获取资源文件的路径?


1
因此,该类只接收一个文件路径(配置文件)。 - noripcord
3
你可能应该让那个类处理一个输入流,你可以从任意来源获取输入流。 - Carl Manaster
1
是的,我知道。但用另一种方式会更清晰、更简洁。不过还是谢谢你。 - noripcord
1
你是在尝试做类似于这个问题中的选项#4吗?https://dev59.com/j3RA5IYBdhLWcg3w8SiJ#775565 - erickson
1
嗯,我遇到了相同的问题。我想要能够将一个图像捆绑在JAR中,然后在运行时使用HTML在JTextPanel中使用该图像。HTML需要相对路径(并设置文档基础为适当的内容)或绝对路径。您可以获取JAR文件中的文件路径吗? - twolfe18
显示剩余3条评论
17个回答

79

这是有意为之的。"文件"的内容可能无法作为文件提供。记住,你正在处理可能是JAR文件或其他类型资源的类和资源。例如,类加载器不必提供文件句柄来访问该资源,例如JAR文件可能没有在文件系统中展开为单独的文件。

如果绝对必要,可以通过将流复制到临时文件中并执行相同操作来获得java.io.File,以完成通过获取 java.io.File 可能实现的任何操作。


6
在调用资源时可以添加'rsrc:'前缀以打开它,例如new File("rsrc:filename.txt"),这将加载存储在jar包根目录中的filename.txt文件。请注意,不要改变原文意思。 - gipsh

76

加载资源时,请确保注意以下两点之间的区别:

getClass().getClassLoader().getResource("com/myorg/foo.jpg") //relative path

getClass().getResource("/com/myorg/foo.jpg")); //note the slash at the beginning

我猜,这种混淆是导致加载资源时大部分问题的原因。


而且,当您加载图像时,使用getResourceAsStream() 更容易:

BufferedImage image = ImageIO.read(getClass().getResourceAsStream("/com/myorg/foo.jpg"));

当你确实需要从JAR归档中加载一个(非图像)文件时,你可以尝试这样做:

File file = null;
String resource = "/com/myorg/foo.xml";
URL res = getClass().getResource(resource);
if (res.getProtocol().equals("jar")) {
    try {
        InputStream input = getClass().getResourceAsStream(resource);
        file = File.createTempFile("tempfile", ".tmp");
        OutputStream out = new FileOutputStream(file);
        int read;
        byte[] bytes = new byte[1024];

        while ((read = input.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.close();
        file.deleteOnExit();
    } catch (IOException ex) {
        Exceptions.printStackTrace(ex);
    }
} else {
    //this will probably work in your IDE, but not from a JAR
    file = new File(res.getFile());
}

if (file != null && !file.exists()) {
    throw new RuntimeException("Error: File " + file + " not found!");
}

3
这对我有用。确保你将要读取的文件放在“bin”文件夹中,并在使用“/com/myorg/filename.ext”路径之前进入加载资源的类所在的目录。 - rayryeng
你能解释一下这句话的意思吗?“最好使用getResourceAsStream()方法来加载资源。”这样做如何解决问题? - Luca S.
@LucaS。这只是针对图像的情况,抱歉没有表述清楚。虽然这种方法有点hacky,但应该是平台无关的。 - Tombart
我正在从我的jar文件中读取一个wav文件,并像您的示例一样将其写入我的tmp目录。但是这些文件是不同的。发生了什么事,我该怎么做才能防止这种情况发生? - tibi
没关系,在我的pom文件里我忘记排除过滤WAV文件了 :'( - tibi
显示剩余3条评论

37

答案很简单 -

String path = this.getClass().getClassLoader().getResource(<resourceFileName>).toExternalForm()

基本上,getResource 方法会返回 URL。通过调用 toExternalForm() 方法,您可以从该 URL 中提取路径。


11
当我从我的环境(IntelliJ)中运行时,这将生成一个简单的文件 URL,在所有情况下都可以工作。然而,当从 jar 包本身运行时,我会得到一个类似于 jar:file:/path/to/jar/jarname.jar!/file_in_jar.mp4 的 URI。并非所有东西都能使用以 jar 开头的 URI。JavaFX Media 就是个例子。 - Noah Ternullo
2
我最喜欢这个答案。当资源在一个jar文件中时,大多数情况下直接获取InputStream可能更好,但如果你真的需要路径,这就是可行的方法。我需要路径来提供给第三方对象。所以,谢谢! - Mario
1
以上解决方案在IDE中不起作用,在Intellij中它会将“file:/”添加到路径中,这在jar包中有效但在IDE中无效。 - Tayab Hussain

13

我花了一段时间来解决这个问题,因为我发现所有我找到的解决方法都不起作用,真是奇怪!工作目录经常不是JAR文件所在的目录,特别是如果JAR文件(或任何程序)在Windows下从“开始菜单”中运行。所以这就是我做的事情,它适用于从JAR文件外部运行的.class文件,就像它适用于JAR文件一样。(我只在Windows 7下测试过。)

try {
    //Attempt to get the path of the actual JAR file, because the working directory is frequently not where the file is.
    //Example: file:/D:/all/Java/TitanWaterworks/TitanWaterworks-en.jar!/TitanWaterworks.class
    //Another example: /D:/all/Java/TitanWaterworks/TitanWaterworks.class
    PROGRAM_DIRECTORY = getClass().getClassLoader().getResource("TitanWaterworks.class").getPath(); // Gets the path of the class or jar.

    //Find the last ! and cut it off at that location. If this isn't being run from a jar, there is no !, so it'll cause an exception, which is fine.
    try {
        PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(0, PROGRAM_DIRECTORY.lastIndexOf('!'));
    } catch (Exception e) { }

    //Find the last / and cut it off at that location.
    PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(0, PROGRAM_DIRECTORY.lastIndexOf('/') + 1);
    //If it starts with /, cut it off.
    if (PROGRAM_DIRECTORY.startsWith("/")) PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(1, PROGRAM_DIRECTORY.length());
    //If it starts with file:/, cut that off, too.
    if (PROGRAM_DIRECTORY.startsWith("file:/")) PROGRAM_DIRECTORY = PROGRAM_DIRECTORY.substring(6, PROGRAM_DIRECTORY.length());
} catch (Exception e) {
    PROGRAM_DIRECTORY = ""; //Current working directory instead.
}

10

这是来自用户Tombart的相同代码,添加了流刷新和关闭操作,以避免从jar资源复制不完整的临时文件内容,并具有唯一的临时文件名。

File file = null;
String resource = "/view/Trial_main.html" ;
URL res = getClass().getResource(resource);
if (res.toString().startsWith("jar:")) {
    try {
        InputStream input = getClass().getResourceAsStream(resource);
        file = File.createTempFile(new Date().getTime()+"", ".html");
        OutputStream out = new FileOutputStream(file);
        int read;
        byte[] bytes = new byte[1024];

        while ((read = input.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.flush();
        out.close();
        input.close();
        file.deleteOnExit();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
} else {
    //this will probably work in your IDE, but not from a JAR
    file = new File(res.getFile());
}
         

8
如果netclient.p在JAR文件中,它将没有路径,因为该文件位于其他文件内部。在这种情况下,你能得到的最佳路径实际上是file:/path/to/jarfile/bot.jar!/config/netclient.p

当我尝试将这种格式的URL(...bot.jar!/config/...)转换为URI时,它会说路径不是分层结构。 - gEdringer

7

您需要了解jar文件中的路径。
只需使用相对路径即可。因此,如果您有一个文件(myfile.txt),位于\src\main\resources目录下的foo.jar中(maven风格)。 您可以这样引用它:

src/main/resources/myfile.txt

如果您使用 jar -tvf myjar.jar 命令来转储您的jar文件,您将看到输出并且相对路径是在jar文件中使用斜线(/)。

你确实必须使用正斜杠,即使在Windows上也是如此。这意味着你不能使用File.separator - str

5
在我的情况下,我使用了URL对象而不是路径(Path)。
文件(File)
File file = new File("my_path");
URL url = file.toURI().toURL();

使用类加载器在类路径中获取资源

URL url = MyClass.class.getClassLoader().getResource("resource_name")

当我需要阅读内容时,可以使用以下代码:

InputStream stream = url.openStream();

您可以使用InputStream来访问内容。

4

请按照以下代码进行操作!

/src/main/resources/file

streamToFile(getClass().getClassLoader().getResourceAsStream("file"))

public static File streamToFile(InputStream in) {
    if (in == null) {
        return null;
    }

    try {
        File f = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
        f.deleteOnExit();

        FileOutputStream out = new FileOutputStream(f);
        byte[] buffer = new byte[1024];

        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }

        return f;
    } catch (IOException e) {
        LOGGER.error(e.getMessage(), e);
        return null;
    }
}

1
这是我能够找到的唯一解决方案,而且我已经为此工作了数小时。谢谢! - Randy

2
一个文件是对文件系统中文件的抽象,而文件系统并不知道JAR文件的内容。
尝试使用URI,我认为有一个jar://协议可能对您有用。

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