如何从Java的jar文件中读取资源文件?

64

我试图从一个作为桌面应用程序运行的独立jar包中访问另一个jar包中的XML文件。我可以获取到所需文件的URL,但是当我将它作为字符串传递给FileReader时,我收到了一个FileNotFoundException,显示“文件名、目录名或卷标语法不正确。”

需要指出的是,我没有任何问题读取相同jar包中的图像资源,将URL传递给ImageIcon构造函数。这似乎表明我使用的获取URL的方法是正确的。

URL url = getClass().getResource("/xxx/xxx/xxx/services.xml");
ServicesLoader jsl = new ServicesLoader( url.toString() );

在 ServicesLoader 类内部,我有

XMLReader xr = XMLReaderFactory.createXMLReader();
xr.setContentHandler( this );
xr.setErrorHandler( this );
xr.parse( new InputSource( new FileReader( filename )));

使用这种技术读取XML文件有什么问题吗?


你能发布可运行的代码吗? - PunkKDB
使用文件API时,请确保使用Unix文件路径分隔符。 - TheRealChx101
9个回答

71

@JVerstry,您的回答对我非常有帮助,可惜在这里被低估了。 - GreenThor

5
你没有说明这是桌面应用还是Web应用。如果是桌面应用,我建议使用适当的ClassLoader的getResourceAsStream()方法; 如果是Web应用,则使用Context对象。

4
看起来您正在将URL.toString的结果作为FileReader构造函数的参数使用。 URL.toString有点问题,通常应使用url.toURI().toString()。无论如何,该字符串都不是文件路径。
相反,您应该:
  • URL传递给ServicesLoader,让它调用openStream或类似方法。
  • 使用Class.getResourceAsStream并只是传递流,可能在InputSource中。 (记得检查null,因为API有点混乱。)

4
问题在于我在调用XMLReader的parse方法时走了一步过远。parse方法接受一个InputSource,所以甚至没有必要使用FileReader。将上面代码的最后一行改为:
xr.parse( new InputSource( filename ));

运作得非常好。


4
我要指出的一个问题是,如果相同的资源在多个jar文件中,该怎么办。 假设您想读取/org/node/foo.txt,但不是从一个文件中,而是从每个jar文件中读取。
我之前遇到过几次这个问题。 我曾希望在JDK 7中有人编写了一个classpath文件系统,但遗憾的是还没有。
Spring有Resource类,可以很好地加载类路径资源。
我编写了一个小型原型来解决从多个jar文件中读取资源的问题。该原型并未处理每个边缘情况,但它确实可以处理在jar文件中的目录中查找资源的情况。
我已经使用Stack Overflow很长时间了。这是我记得回答问题的第二个答案,所以请原谅我如果我讲得太长(这是我的天性)。
这是一个原型资源阅读器。该原型缺乏强大的错误检查。
我有两个设置的原型jar文件。
 <pre>
         <dependency>
              <groupId>invoke</groupId>
              <artifactId>invoke</artifactId>
              <version>1.0-SNAPSHOT</version>
          </dependency>

          <dependency>
               <groupId>node</groupId>
               <artifactId>node</artifactId>
               <version>1.0-SNAPSHOT</version>
          </dependency>

每个jar文件都有一个位于/org/node/下的名为resource.txt的文件。
这只是一个使用classpath://的处理程序原型。 我还在本地资源中为该项目添加了一个resource.foo.txt文件。
它会收集它们并将它们打印出来。
   

    package com.foo;
import java.io.File; import java.io.FileReader; import java.io.InputStreamReader; import java.io.Reader; import java.net.URI; import java.net.URL; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile;
/** * 原型资源读取器。 * 这个原型没有错误检查。 * * 我有两个设置好的原型jar文件。 * <pre> * <dependency> * <groupId>invoke</groupId> * <artifactId>invoke</artifactId> * <version>1.0-SNAPSHOT</version> * </dependency> * * <dependency> * <groupId>node</groupId> * <artifactId>node</artifactId> * <version>1.0-SNAPSHOT</version> * </dependency> * </pre> * 这些jar文件下都会有一个名为resource.txt的文件夹,路径是/org/node/。 * <br /> * 这只是一个处理类路径的handler演示. * 本项目还有一个resource.foo.txt文件在本地资源中。 * <br /> */ public class ClasspathReader {
public static void main(String[] args) throws Exception {
/* 这个项目包含了两个jar文件,每个jar文件都有一个位于/org/node/的资源文件。 表示为someResource。 */ String namespace = "resource";
//someResource是类路径。 String someResource = args.length > 0 ? args[0] : //"classpath:///org/node/resource.txt"; It works with files "classpath:///org/node/"; //It also works with directories
URI someResourceURI = URI.create(someResource);
System.out.println("URI of resource = " + someResourceURI);
someResource = someResourceURI.getPath();
System.out.println("PATH of resource =" + someResource);
boolean isDir = !someResource.endsWith(".txt");
/** 类路径下的资源文件实际上并不以“/”开头。 * 逻辑上是这样,但实际上需要去掉它。 * 这是类路径资源的已知行为。 * 如果资源文件在JAR文件中,则使用斜杠(/)。 * 通过去掉起始斜杠,可以在所有情况下正常工作。 */ if (someResource.startsWith("/")) { someResource = someResource.substring(1); }
/* 使用ClassLoader查找具有该名称的所有资源。 查找匹配所需位置的所有资源。 */ Enumeration resources = null;
/* 首先检查上下文类加载器。如果有可用的,请始终使用此处。 */ try { resources = Thread.currentThread().getContextClassLoader().getResources(someResource); } catch (Exception ex) { ex.printStackTrace(); }
if (resources == null || !resources.hasMoreElements()) { resources = ClasspathReader.class.getClassLoader().getResources(someResource); }
//现在迭代类路径下资源的URL while (resources.hasMoreElements()) { URL resource = resources.nextElement();
/* 如果资源是文件,则可以使用正常机制来扫描目录。 */ if (resource.getProtocol().equals("file")) { //如果是文件,则可以用普通的方式处理它。 handleFile(resource, namespace); continue; }
System.out.println("Resource " + resource);
/* 将字符串拆分为以下格式: jar:file:/Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar!/org/node/ 到这个 this /Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar 和这个 /org/node/ */ String[] split = resource.toString().split(":");

您可以在此处查看完整示例及样本输出。


1
这里是一个示例代码,演示如何在jar文件中正确读取文件(在此情况下,是当前执行的jar文件)。
如果不是当前运行的jar文件,请将executable更改为您的jar文件路径。
然后将filePath更改为您想要在jar文件中使用的文件路径。例如,如果您的文件在

someJar.jar\img\test.gif

将 filePath 设置为 "img\test.gif"。
File executable = new File(BrowserViewControl.class.getProtectionDomain().getCodeSource().getLocation().toURI());
JarFile jar = new JarFile(executable);
InputStream fileInputStreamReader = jar.getInputStream(jar.getJarEntry(filePath));
byte[] bytes = new byte[fileInputStreamReader.available()];

int sizeOrig = fileInputStreamReader.available();
int size = fileInputStreamReader.available();
int offset = 0;
while (size != 0){
    fileInputStreamReader.read(bytes, offset, size);
    offset = sizeOrig - fileInputStreamReader.available();
    size = fileInputStreamReader.available();
}

0

如果您需要广泛使用资源,可以考虑使用Commons VFS

还支持以下功能: * 本地文件 * FTP、SFTP * HTTP 和 HTTPS * 临时文件 "normal FS backed" * Zip、Jar 和 Tar(未压缩、tgz 或 tbz2) * gzip 和 bzip2 * 资源 * ram - "ramdrive" * mime

还有JBoss VFS - 但文档不太全。


0

除了你的技术之外,为什么不使用标准的Java JarFile类来获取你想要的引用呢?从那里开始,你的大部分问题应该都会消失。


0

我有两个 CSV 文件,用于读取数据。Java 程序以可运行的 jar 文件形式导出。当你导出它时,你会发现它并没有将你的资源一起导出。

我在 Eclipse 的项目下添加了一个名为 data 的文件夹,在该文件夹中存储了我的 csv 文件。

当我需要引用这些文件时,我会这样做...

private static final String ZIP_FILE_LOCATION_PRIMARY = "free-zipcode-database-Primary.csv";
private static final String ZIP_FILE_LOCATION = "free-zipcode-database.csv";

private static String getFileLocation(){
    String loc = new File("").getAbsolutePath() + File.separatorChar +
        "data" + File.separatorChar;
    if (usePrimaryZipCodesOnly()){              
        loc = loc.concat(ZIP_FILE_LOCATION_PRIMARY);
    } else {
        loc = loc.concat(ZIP_FILE_LOCATION);
    }
    return loc;
}

然后,当您将jar文件放置在可以通过命令行运行的位置时,请确保将包含资源的数据文件夹添加到与jar文件相同的位置。


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