如何在Java中从jar文件中读取文件?

67

我想读取一个在我的类路径中包含的jar内的XML文件。如何读取任何包含在jar中的文件?


请返回翻译文本。 - yegor256
如果您想从JAR文件中读取目录中任意数量的文件,请参见[Stackoverflow-Link](https://dev59.com/-IPba4cB1Zd3GeqPzfbN#39818817) - Michael Hegner
7个回答

110

如果您想从应用程序内部读取该文件,请使用以下方法:

InputStream input = getClass().getResourceAsStream("/classpath/to/my/file");

这个路径以“/”开头,但它并不是您文件系统中的路径,而是您类路径中的路径。所以如果您的文件在类路径“org.xml”下,并且名为myxml.xml,则您的路径看起来像“/org/xml/myxml.xml”。

InputStream读取文件内容。如果需要,您可以将其包装成Reader。


60

啊,这是我最喜欢的主题之一。你可以通过类路径加载资源的基本上有两种方法:

Class.getResourceAsStream(resource)

ClassLoader.getResourceAsStream(resource)

(还有其他方法可以类似地获取资源的URL,然后打开连接,但这是两种直接的方法)。

第一种方法实际上是委托给第二种方法,在混淆资源名称后。基本上有两种资源名称:绝对路径(例如“/ path / to / resource / resource”)和相对路径(例如“resource”)。绝对路径以“/”开头。

以下示例应该能说明问题。考虑一个名为com.example.A的类。在类路径中,考虑位于/com/example/nested和/top的两个资源。以下程序显示了访问这两个资源的9种可能方式:

package com.example;

public class A {
public static void main(String args[]) {
// Class.getResourceAsStream Object resource = A.class.getResourceAsStream("nested"); System.out.println("1: A.class nested=" + resource);
resource = A.class.getResourceAsStream("/com/example/nested"); System.out.println("2: A.class /com/example/nested=" + resource);
resource = A.class.getResourceAsStream("top"); System.out.println("3: A.class top=" + resource);
resource = A.class.getResourceAsStream("/top"); System.out.println("4: A.class /top=" + resource);
// ClassLoader.getResourceAsStream ClassLoader cl = A.class.getClassLoader(); resource = cl.getResourceAsStream("nested"); System.out.println("5: cl nested=" + resource);
resource = cl.getResourceAsStream("/com/example/nested"); System.out.println("6: cl /com/example/nested=" + resource); resource = cl.getResourceAsStream("com/example/nested"); System.out.println("7: cl com/example/nested=" + resource);
resource = cl.getResourceAsStream("top"); System.out.println("8: cl top=" + resource);
resource = cl.getResourceAsStream("/top"); System.out.println("9: cl /top=" + resource); }
}

该程序的输出为:

1: A.class nested=java.io.BufferedInputStream@19821f
2: A.class /com/example/nested=java.io.BufferedInputStream@addbf1
3: A.class top=null
4: A.class /top=java.io.BufferedInputStream@42e816
5: cl nested=null
6: cl /com/example/nested=null
7: cl com/example/nested=java.io.BufferedInputStream@9304b1
8: cl top=java.io.BufferedInputStream@190d11
9: cl /top=null

大多数情况下都是按预期进行的。 第3个案例失败,因为相对于Class的类相关解析是以“/com/example/top”为基础,但“/top”表示其本身。

案例5失败,因为类加载器相对解析是相对于类加载器的。 但是,出人意料的是,案例6也失败了:可以预期“/com/example/nested”会正确解析。 要通过类加载器访问嵌套资源,您需要使用Case-7,即嵌套路径相对于类加载器的根目录。 同样,案例9失败,但案例8成功。

请记住:对于java.lang.Class,getResourceAsStream()确实委托给类加载器:

     public InputStream getResourceAsStream(String name) {
        name = resolveName(name);
        ClassLoader cl = getClassLoader0();
        if (cl==null) {
            // A system class.
            return ClassLoader.getSystemResourceAsStream(name);
        }
        return cl.getResourceAsStream(name);
    }

因此,在重要的resolveName()行为。

最后,由于实际控制getResourceAsStream的是加载类的类加载器的行为,并且类加载器通常是自定义加载程序,因此资源加载规则可能会更加复杂。例如Web应用程序,从Web应用程序的上下文中的WEB-INF/classes或WEB-INF/lib加载,但不从其他被隔离的Web应用程序加载。此外,行为良好的类加载器会委托给父代,因此在类路径中重复的资源


这本质上是我给出的相同答案,但更详细地解释了。+1 - Mnementh
这里是之前提到的 resolveName 方法:https://gist.github.com/briangordon/5374626 - Rag

9

首先检查你的类加载器。

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

if (classLoader == null) {
    classLoader = Class.class.getClassLoader();
}

classLoader.getResourceAsStream("xmlFileNameInJarFile.xml");

// xml file location at xxx.jar
// + folder
// + folder
// xmlFileNameInJarFile.xml

5

JAR基本上就是一个ZIP文件,因此可以将其视为ZIP文件。以下是从WAR文件中提取一个文件(也将其视为ZIP文件)并输出字符串内容的示例。对于二进制文件,您需要修改提取过程,但是有很多示例可供参考。

public static void main(String args[]) {
    String relativeFilePath = "style/someCSSFile.css";
    String zipFilePath = "/someDirectory/someWarFile.war";
    String contents = readZipFile(zipFilePath,relativeFilePath);
    System.out.println(contents);
}

public static String readZipFile(String zipFilePath, String relativeFilePath) {
    try {
        ZipFile zipFile = new ZipFile(zipFilePath);
        Enumeration<? extends ZipEntry> e = zipFile.entries();

        while (e.hasMoreElements()) {
            ZipEntry entry = (ZipEntry) e.nextElement();
            // if the entry is not directory and matches relative file then extract it
            if (!entry.isDirectory() && entry.getName().equals(relativeFilePath)) {
                BufferedInputStream bis = new BufferedInputStream(
                        zipFile.getInputStream(entry));
                // Read the file
                    // With Apache Commons I/O
                 String fileContentsStr = IOUtils.toString(bis, "UTF-8");

                    // With Guava
                //String fileContentsStr = new String(ByteStreams.toByteArray(bis),Charsets.UTF_8);
                // close the input stream.
                bis.close();
                return fileContentsStr;
            } else {
                continue;
            }
        }
    } catch (IOException e) {
        logger.error("IOError :" + e);
        e.printStackTrace();
    }
    return null;
}

在这个例子中,我正在使用Apache Commons I/O,如果您正在使用Maven,则需要添加以下依赖:
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

0

这也适用于Spring

    ClassPathResource resource = new ClassPathResource("/file.txt", MainApplication.class); //resources folder
    InputStream inputStream = resource.getInputStream();

    File file = new File("file.txt");
    FileUtils.copyInputStreamToFile(inputStream, file);

0

仅为完整起见,最近在Jython邮件列表上出现了一个问题,其中一个答案引用了这个线程。

问题是如何从Jython中调用包含在.jar文件中的Python脚本,建议的答案如下(使用上面某个答案中解释的"InputStream"):

PythonInterpreter.execfile(InputStream)

0
这是我从一个jar文件中读取文件的方法。
我想要读取的文件位于jar文件之外,是应用程序资源文件夹下的一个文件。
try(InputStream inputStream = this.getClass()
                .getClassLoader()
                .getResourceAsStream("common/test.yaml")) {
            //Your code
        } catch (Exception e) {
            LOGGER.error(e);
        }

如我所说,该文件位于资源文件夹下,而且我还有子文件夹。

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