获取.jar文件内的目录

8

我想访问我的jar文件内的一个目录。我想要遍历目录中的每个文件。例如,我尝试使用以下代码:

URL imagesDirectoryURL=getClass().getClassLoader().getResource("Images");

if(imagesFolderURL!=null)
{
    File imagesDirectory= new File(imagesDirectoryURL.getFile());
}

如果我测试这个小程序,它可以正常工作。但是一旦我把内容放到jar文件中,由于几个原因它就不能工作了。 如果我使用这段代码,URL总是指向jar包外面,所以我必须把Images目录放在那里。 但是如果我使用new File(imagesDirectoryURL.toURI());,它在jar包内部不起作用,因为我会得到错误URI not hierarchical。 我确定目录在jar包内部存在。 我应该如何获取jar包内的Images内容?
8个回答

6
这里有一个解决方案,如果你使用Java 7,应该可以正常工作...“诀窍”在于使用新的文件API。Oracle JDK提供了一个FileSystem实现,可以用于查看/修改ZIP文件,包括jar文件!
首先,获取System.getProperty("java.class.path", "."),并根据:进行拆分;这将为您提供定义的类路径中的所有条目。
其次,定义一个方法来从类路径条目中获取FileSystem
private static final Map<String, ?> ENV = Collections.emptyMap();

//

private static FileSystem getFileSystem(final String entryName)
    throws IOException
{
    final String uri = entryName.endsWith(".jar") || entryName.endsWith(".zip"))
        ? "jar:file:" + entryName : "file:" + entryName;
    return FileSystems.newFileSystem(URI.create(uri), ENV);
}

然后创建一个方法来判断文件系统中是否存在某个路径:
private static boolean pathExists(final FileSystem fs, final String needle)
{
    final Path path = fs.getPath(needle);
    return Files.exists(path);
}

请使用它来定位您的目录。
一旦您拥有了正确的FileSystem,请使用上面提到的.getPath()遍历您的目录,并使用Files.newDirectoryStream()打开一个DirectoryStream
在使用完FileSystem后,请不要忘记使用.close()关闭它!
以下是演示如何读取jar根目录中所有条目的示例main()
public static void main(final String... args)
    throws IOException
{
    final Map<String, ?> env = Collections.emptyMap();
    final String jarName = "/opt/sunjdk/1.6/current/jre/lib/plugin.jar";
    final URI uri = URI.create("jar:file:" + jarName);
    final FileSystem fs = FileSystems.newFileSystem(uri, env);
    final Path dir = fs.getPath("/");
    for (Path entry : Files.newDirectoryStream(dir))
        System.out.println(entry);
}

5
在Jar文件中,路径是路径,不是实际的目录,就像你在文件系统上使用它们一样。要获取Jar文件特定路径下的所有资源:
  • 获得指向该Jar文件的URL
  • URL获取InputStream
  • InputStream构造ZipInputStream
  • 迭代每个ZipEntry,查找与所需路径匹配的条目。

..如果我的Applet不在那个jar中,我还能测试吗?或者我必须编写两种方法来获取我的图片?

ZipInputStream无法处理文件系统上目录中的松散资源。但是,我强烈建议使用构建工具(如Ant)来构建(编译/打包/签名等)Applet。编写构建脚本并进行检查可能需要一个小时左右,但之后您只需按下几个键并等待几秒钟即可构建项目。

如果我想测试我的Aplet,每次都必须提取和签署我的jar文件会很麻烦

我不确定你的意思。‘提取’是从哪里开始的?如果我没有表达清楚,一个沙盒应用程序可以通过在archive属性中提到的任何JAR文件加载资源。另一件事情,你可以做的是将资源JAR与应用程序JAR分开。资源通常比代码更少更改,因此您的构建可能能够采取一些捷径。

我认为我真的需要考虑将我的图像放入jar之外的单独目录中。

如果你指的是服务器上,除了向服务器寻求帮助,没有实际的方法来获取图像文件列表。例如,某些服务器被不安全地设置为为任何没有默认文件(如index.html)的目录生成基于HTML的“文件列表”。
我只有一个jar文件,其中包含我的类、图片和声音。
好的-考虑将声音和图像移到单独的Jar中。或者至少将它们放在没有压缩的Jar中。虽然Zip压缩技术对类很有效,但对于(已经压缩的)媒体格式来说,它们的压缩效率较低。
我必须签署它,因为我使用“Preferences”类保存用户设置。
对于小程序,可以使用cookie等替代“Preferences”。对于插件2架构小程序,可以使用Java Web Start启动嵌入在浏览器中的小程序。JWS提供了PersistenceService。这是我小小的 PersistenceService演示
说到JWS,这就引出了一个问题:你是否绝对确定这个游戏作为小程序而不是应用程序(例如使用JFrame)通过JWS启动会更好呢?

小程序会给你带来无尽的压力,自从Java 1.2引入以来,JWS一直提供了PersistenceService


如果我使用这样的代码,当我的Applet不在那个jar文件中时,我还能测试它吗?或者我必须编写两种方式来获取我的图像?如果我想要测试我的Applet,每次都必须提取和签署我的jar文件,那将会非常烦人:S如果是这样的话,我认为我真的必须考虑将我的图像放入jar文件外的单独目录中... - Ivorius
首先,当我测试我的应用程序时,它目前运行良好。但是我在结构上进行了一些更改。 以前,我只是使用“new ImageIcon(this.getClass().getClassLoader().getResource("anImage.jpg")”从jar包内部获取我的图像。现在我建立了一个类来处理加载它们。这个类在开始时加载所有的图像,因此不需要进一步的加载时间。通过提问,我只是想知道是否有一种同样简单的方法可以从jar包内部获取“目录”。 - Ivorius
我只有一个jar文件,其中包含我的类、图像和声音。因此,除非我发布另一个版本或想在另一台计算机上测试它,否则该jar文件不存在。我必须对其进行签名,因为我使用“Preferences”类来保存用户设置。 关于您的第二个问题:我没有任何客户端或服务器,Applet是自己运行的。这是我正在开发的游戏。 希望我现在表达得更清楚了 :) - Ivorius
谢谢你提供的所有技巧。看起来我会把我的媒体文件(图片、声音)放到另一个目录中,并查看 PersistenceServe。我本来想把它做成一个 Applet,因为直接从浏览器上传并播放可能很酷,但也许我会改回 JFrame。 - Ivorius
@Ava getClass().getClassLoader().getResource("/some/path/to/ResourceInJar.txt"); 将获取到 Jar 包中资源的 URL。可以从第一个 URL 中获取 Jar 的 URL(检查第一个 URL 的输出)。但是.. Blah.class.getResourceAsStream(path) ..你认为我有水晶球吗?!? 我甚至不知道 path 的值。发布一个新问题,包括 [mcve] 以及资源路径的描述。 - Andrew Thompson

2
您可以使用Spring提供的PathMatchingResourcePatternResolver
public class SpringResourceLoader {

    public static void main(String[] args) throws IOException {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

        // Ant-style path matching
        Resource[] resources = resolver.getResources("/Images/**");

        for (Resource resource : resources) {
            System.out.println("resource = " + resource);
            InputStream is = resource.getInputStream();
            BufferedImage img =  ImageIO.read(is);
            System.out.println("img.getHeight() = " + img.getHeight());
            System.out.println("img.getWidth() = " + img.getWidth());
        }
    }
}

我对返回的Resource没有做任何花哨的处理,但你应该明白了。

如果使用maven,请将以下内容添加到依赖项中:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>

这将直接在Eclipse/NetBeans/IntelliJ中运行,并且在部署的jar文件中也可以正常工作。

在IntelliJ中运行会得到以下输出:

resource = file [C:\Users\maba\Development\stackoverflow\Q12016222\target\classes\pictures\BMW-R1100S-2004-03.jpg]
img.getHeight() = 768
img.getWidth() = 1024

在命令行使用可执行的jar文件运行会给我以下输出:
C:\Users\maba\Development\stackoverflow\Q12016222\target>java -jar Q12016222-1.0-SNAPSHOT.jar
resource = class path resource [pictures/BMW-R1100S-2004-03.jpg]
img.getHeight() = 768
img.getWidth() = 1024

0

如果我理解您的问题,您想要检查jar文件中的目录并检查该目录中的所有文件。您可以尝试以下操作:

JarInputStream jar = new JarInputStream(new FileInputStream("D:\\x.jar"));
    JarEntry jarEntry ;
    while(true)
        {
         jarEntry = jar.getNextJarEntry();
         if(jarEntry != null)
         {

            if(jarEntry.isDirectory() == false)
            {
        String str = jarEntry.getName();
                if(str.startsWith("weblogic/xml/saaj"))
        {
            anything which comes here are inside weblogic\xml\saaj directory
        }
        }

     }
    }    

有没有一种简单的方法可以将这段代码轻松地应用到我的代码所在的jar文件中(当然,我不知道确切的路径,因为每次移动Applett时它总是会改变)。 此外,如果我只想在Eclipse中进行测试,它将无法正常工作。那么我需要两个代码部分吗? - Ivorius
在这种情况下,你可以将JAR名称作为参数传递。是的,目录路径会根据操作系统Unix/Windows等而发生变化。 - Swagatika

0

你在这里寻找的可能是JarEntry列表...我在研究生期间做过类似的工作...你可以在这里获取修改后的类(http://code.google.com/p/marcellodesales-cs-research/source/browse/trunk/grad-ste-ufpe-brazil/ptf-add-on-dev/src/br/ufpe/cin/stp/global/filemanager/JarFileContentsLoader.java)请注意,URL包含一个不使用泛型的旧Java类...

该类返回一个具有协议“jar:file:/”的URL集合,用于给定的令牌...

package com.collabnet.svnedge.discovery.client.browser.util;

import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class JarFileContentsLoader {

    private JarFile jarFile;

    public JarFileContentsLoader(String jarFilePath) throws IOException {
        this.jarFile = new JarFile(jarFilePath);
    }

    /**
     * @param existingPath an existing path string inside the jar.
     * @return the set of URL's from inside the Jar (whose protocol is "jar:file:/"
     */
    public Set<URL> getDirEntries(String existingPath) {
        Set<URL> set = new HashSet<URL>();
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            String element = entries.nextElement().getName();
            URL url = getClass().getClassLoader().getResource(element);
            if (url.toString().contains("jar:file")
                    && !element.contains(".class")
                    && element.contains(existingPath)) {
                set.add(url);
            }
        }
        return set;
    }

    public static void main(String[] args) throws IOException {
        JarFileContentsLoader jarFileContents = new JarFileContentsLoader(
                "/u1/svnedge-discovery/client-browser/lib/jmdns.jar");
        Set<URL> entries = jarFileContents.getDirEntries("impl");
        Iterator<URL> a = entries.iterator();
        while (a.hasNext()) {
            URL element = a.next();
            System.out.println(element);
        }
    }

}

输出结果将为:

jar:file:/u1/svnedge-discovery/client-browser/lib/jmdns.jar!/javax/jmdns/impl/constants/
jar:file:/u1/svnedge-discovery/client-browser/lib/jmdns.jar!/javax/jmdns/impl/tasks/state/
jar:file:/u1/svnedge-discovery/client-browser/lib/jmdns.jar!/javax/jmdns/impl/tasks/resolver/
jar:file:/u1/svnedge-discovery/client-browser/lib/jmdns.jar!/javax/jmdns/impl/
jar:file:/u1/svnedge-discovery/client-browser/lib/jmdns.jar!/javax/jmdns/impl/tasks/

0

希望以下代码示例能对您有所帮助

   Enumeration<URL> inputStream = BrowserFactory.class.getClassLoader().getResources(".");
        System.out.println("INPUT STREAM ==> "+inputStream);
        System.out.println(inputStream.hasMoreElements());
        while (inputStream.hasMoreElements()) {
            URL url = (URL) inputStream.nextElement();
            System.out.println(url.getFile());
        }

0

如果你真的想把JAR文件当作目录来处理,那么请看一下TrueZIP 7。以下类似的代码可能是你想要的:

URL url = ... // whatever
URI uri = url.toURI();
TFile file = new TFile(uri); // File-look-alike in TrueZIP 7
if (file.isDirectory) // true for regular directories AND JARs if the module truezip-driver-file is on the class path
    for (TFile entry : file.listFiles()) // iterate top level directory
         System.out.println(entry.getPath()); // or whatever

致敬, Christian


不需要,TrueZIP File* API只需要JSE6。 - Christian Schlichtherle

0

虽然看起来不错,但这并不是我想要的。我只是把整个Applet放在一个jar文件中,而不是把图像放在另一个文件中。当然,我可以把我的图像放在一个单独的目录(或者,似乎是一个单独的jar文件)中,但是有没有一种方法可以直接从我的jar文件中获取它们呢? - Ivorius
只需指向包含资源的jar文件即可,无需分离。 - Imran

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