Java资源作为文件

131

在Java中,是否有一种方法可以通过类加载器从jar中检索到资源并构建一个File实例?

我的应用程序使用来自jar(默认)或在运行时指定的文件系统目录中的某些文件(用户输入)。我正在寻找一种一致的方法
a)将这些文件作为流加载
b)分别列出用户定义的目录或jar中的目录中的文件

编辑:显然,理想方法是完全避开java.io.File。是否有一种方法可以从类路径加载目录并列出其内容(其中包含的文件/实体)?

6个回答

109

我遇到了同样的问题,并且成功地使用了以下方法:

// Load the directory as a resource
URL dir_url = ClassLoader.getSystemResource(dir_path);
// Turn the resource into a File object
File dir = new File(dir_url.toURI());
// List the directory
String files = dir.list()

50
这种方法不适用于类路径中的 JAR 文件,只适用于文件和目录。 - yegor256
2
如果你的文件在一个jar包中,你可以创建一个FileSystem对象并从中获取Path。然后你可以像平常一样读取它。(参见https://dev59.com/SGEh5IYBdhLWcg3wRRqh#22605905) - mgttlinger

61

ClassLoader.getResourceAsStreamClass.getResourceAsStream 是从资源中加载数据的方法,但是我认为没有任何方式可以"列出"类路径元素的内容。

在某些情况下这可能是不可能的。例如,一个ClassLoader可以根据所请求的资源名称实时生成数据。如果你查看ClassLoader API(它基本上是类路径机制工作的原理),你会发现没有任何东西能够做到你想要的。

如果你知道你实际上有一个jar文件,那么你可以使用ZipInputStream来查找可用的内容。这意味着你需要为目录和jar文件编写不同的代码。

另一种选择是,如果文件是先单独创建的,可以包含一种包含可用资源列表的清单文件。把它打包到jar文件中或将其作为一个文件包含在文件系统中,在向用户提供可选资源之前先加载它。


3
我同意这很让人烦恼 - 但它使得ClassLoader在其他方面更加普适。例如,编写“网络类加载器”很容易,因为Web适合获取文件,但通常不会列出文件。 - Jon Skeet
如果您尝试加载的资源比1MiB大得多,似乎从getResourceAsStream获取的InputStream在该大小后停止检索资源的字节,并且如果它包含在压缩文件系统(如jar)中,则返回0。在这种情况下,您似乎被迫使用getResource并独立加载文件。 - mgttlinger
1
@mgttlinger:对我来说听起来不太可能...如果可以的话,最好发布一个新问题并提供可重现的代码。 - Jon Skeet
问题是由于read方法决定读取多少数据(我不知道这一点)导致的。如果被读取的文件位于常规文件夹中,则似乎会读取整个文件。如果文件在jar包中,因为已经打包,它只会读取部分内容。 - mgttlinger
1
@mgttlinger:不,它不仅会读取其中的一部分 - 但是在每次读取调用时可能不会填充整个缓冲区。与InputStream一样,如果您想读取整个资源,应该循环直到read返回-1。 - Jon Skeet

57

这是我一个应用程序中的一部分代码... 如果符合您的需求,请告诉我。 如果您知道要使用的文件,可以使用此代码。

URL defaultImage = ClassA.class.getResource("/packageA/subPackage/image-name.png");
File imageFile = new File(defaultImage.toURI());

是的,它确实可以,但 toURI() 会失败。 - Olivier Faucheux
2
如果资源在JAR文件中(在类路径上),则此操作将失败。 - Stephen C
具体来说:如果资源位于与执行它的代码不同的JAR文件中。 - undefined

17

构建从JAR中检索的资源的文件实例的可靠方法是将该资源作为流复制到临时文件中(当JVM退出时,临时文件将被删除):

一个可靠的方法是将JAR中检索的资源作为流复制到临时文件中,以此来构建File实例(在JVM退出时会删除临时文件):

public static File getResourceAsFile(String resourcePath) {
    try {
        InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
        if (in == null) {
            return null;
        }

        File tempFile = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
        tempFile.deleteOnExit();

        try (FileOutputStream out = new FileOutputStream(tempFile)) {
            //copy stream
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
        }
        return tempFile;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

4

1
谢谢您的回复。我知道getResourceAsStream,但我不认为我可以通过它从类路径加载目录。 - Mantrum
在Java目录中有一个文件(我猜是UNIX根目录),所以你至少应该尝试一下。 - topchef
我认为要列出目录中的文件,您需要使用java.io.File。您可以使用ClassLoader.findResource查找文件,该方法返回一个URL,可以将其传递给File。 - Nathan Voxland

2

6
尽管这个链接可能回答了问题,但最好在这里包含答案的重要部分,并提供链接作为参考,因为链接可能会更改或链接的页面可能被删除,从而使答案无用。 - Jonas Czech

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