如何列出JAR文件中的文件?

[英]How do I list the files inside a JAR file?


I have this code which reads all the files from a directory.

我有一个从一个目录读取所有文件的代码。

    File textFolder = new File("text_directory");

    File [] texFiles = textFolder.listFiles( new FileFilter() {
           public boolean accept( File file ) {
               return file.getName().endsWith(".txt");
           }
    });

It works great. It fills the array with all the files that end with ".txt" from directory "text_directory".

它的工作原理。它将所有以“。”结尾的文件填充到数组中。txt text_directory“从目录。

How can I read the contents of a directory in a similar fashion within a JAR file?

如何在JAR文件中以类似的方式读取目录的内容?

So what I really want to do is, to list all the images inside my JAR file, so I can load them with:

所以我真正想做的是,列出我的JAR文件中的所有图像,这样我就可以加载它们:

ImageIO.read(this.getClass().getResource("CompanyLogo.png"));

(That one works because the "CompanyLogo" is "hardcoded" but the number of images inside the JAR file could be from 10 to 200 variable length.)

(因为“CompanyLogo”是“硬编码”的,但是JAR文件中的图像数量可能从10到200个不等。)

EDIT

编辑

So I guess my main problem would be: How to know the name of the JAR file where my main class lives?

所以我想我的主要问题是:如何知道我的主类生活的JAR文件的名称?

Granted I could read it using java.util.Zip.

当然,我可以用java.util.Zip来读取它。

My Structure is like this:

我的结构是这样的:

They are like:

他们就像:

my.jar!/Main.class
my.jar!/Aux.class
my.jar!/Other.class
my.jar!/images/image01.png
my.jar!/images/image02a.png
my.jar!/images/imwge034.png
my.jar!/images/imagAe01q.png
my.jar!/META-INF/manifest 

Right now I'm able to load for instance "images/image01.png" using:

现在我可以加载实例“images/image01”。使用png”:

    ImageIO.read(this.getClass().getResource("images/image01.png));

But only because I know the file name, for the rest I have to load them dynamically.

但仅仅因为我知道文件名,其余部分我必须动态加载它们。

13 个解决方案

#1


77  

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
if (src != null) {
  URL jar = src.getLocation();
  ZipInputStream zip = new ZipInputStream(jar.openStream());
  while(true) {
    ZipEntry e = zip.getNextEntry();
    if (e == null)
      break;
    String name = e.getName();
    if (name.startsWith("path/to/your/dir/")) {
      /* Do something with this entry. */
      ...
    }
  }
} 
else {
  /* Fail... */
}

Note that in Java 7, you can create a FileSystem from the JAR (zip) file, and then use NIO's directory walking and filtering mechanisms to search through it. This would make it easier to write code that handles JARs and "exploded" directories.

注意,在Java 7中,您可以从JAR (zip)文件创建一个文件系统,然后使用NIO的目录移动和过滤机制来搜索它。这样可以更容易地编写处理jar和“爆炸”目录的代码。

#2


49  

Code that works for both IDE's and .jar files:

适用于IDE和.jar文件的代码:

import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;

public class ResourceWalker {
    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        Path myPath;
        if (uri.getScheme().equals("jar")) {
            FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap());
            myPath = fileSystem.getPath("/resources");
        } else {
            myPath = Paths.get(uri);
        }
        Stream<Path> walk = Files.walk(myPath, 1);
        for (Iterator<Path> it = walk.iterator(); it.hasNext();){
            System.out.println(it.next());
        }
    }
}

#3


18  

erickson's answer worked perfectly:

埃里克森的回答完美工作:

Here's the working code.

这是工作代码。

CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
List<String> list = new ArrayList<String>();

if( src != null ) {
    URL jar = src.getLocation();
    ZipInputStream zip = new ZipInputStream( jar.openStream());
    ZipEntry ze = null;

    while( ( ze = zip.getNextEntry() ) != null ) {
        String entryName = ze.getName();
        if( entryName.startsWith("images") &&  entryName.endsWith(".png") ) {
            list.add( entryName  );
        }
    }

 }
 webimages = list.toArray( new String[ list.size() ] );

And I have just modify my load method from this:

我从这里修改了load方法:

File[] webimages = ... 
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex].getName() ));

To this:

:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));

#4


5  

So I guess my main problem would be, how to know the name of the jar where my main class lives.

所以我想我的主要问题是,如何知道我主要的课堂生活的罐子的名字。

Assuming that your project is packed in a Jar (not necessarily true!), you can use ClassLoader.getResource() or findResource() with the class name (followed by .class) to get the jar that contains a given class. You'll have to parse the jar name from the URL that gets returned (not that tough), which I will leave as an exercise for the reader :-)

假设您的项目被打包在一个Jar中(不一定是正确的!),您可以使用ClassLoader.getResource()或findResource()和类名(后面是.class)来获取包含给定类的Jar。您将不得不从返回的URL解析jar名称(不是很困难),我将留给读者一个练习:-)

Be sure to test for the case where the class is not part of a jar.

一定要测试类不是jar的一部分的情况。

#5


5  

I would like to expand on acheron55's answer, since it is a very non-safe solution, for several reasons:

我想扩展一下acheron55的答案,因为这是一个非常不安全的解决方案,原因如下:

  1. It doesn't close the FileSystem object.
  2. 它没有关闭文件系统对象。
  3. It doesn't check if the FileSystem object already exists.
  4. 它不检查文件系统对象是否已经存在。
  5. It isn't thread-safe.
  6. 它不是线程安全的。

This is somewhat a safer solution:

这是一个比较安全的解决方案:

private static ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();

public void walk(String path) throws Exception {

    URI uri = getClass().getResource(path).toURI();
    if ("jar".equals(uri.getScheme()) {
        safeWalkJar(path, uri);
    } else {
        Files.walk(Paths.get(path));
    }
}

private void safeWalkJar(String path, URI uri) throws Exception {

    synchronized (getLock(uri)) {    
        // this'll close the FileSystem object at the end
        try (FileSystem fs = getFileSystem(uri)) {
            Files.walk(fs.getPath(path));
        }
    }
}

private Object getLock(URI uri) {

    String fileName = parseFileName(uri);  
    locks.computeIfAbsent(fileName, s -> new Object());
    return locks.get(fileName);
}

private String parseFileName(URI uri) {

    String schemeSpecificPart = uri.getSchemeSpecificPart();
    return schemeSpecificPart.substring(0, schemeSpecificPart.indexOf("!"));
}

private FileSystem getFileSystem(URI uri) throws IOException {

    try {
        return FileSystems.getFileSystem(uri);
    } catch (FileSystemNotFoundException e) {
        return FileSystems.newFileSystem(uri, Collections.<String, String>emptyMap());
    }
}   

There's no real need to synchronize over the file name; one could simply synchronize on the same object every time (or make the method synchronized), it's purely an optimization.

不需要对文件名进行同步;我们可以每次都在相同的对象上同步(或使方法同步),这纯粹是一个优化。

I would say that this is still a problematic solution, since there might be other parts in the code that use the FileSystem interface over the same files, and it could interfere with them (even in a single threaded application).
Also, it doesn't check for nulls (for instance, on getClass().getResource().

我认为这仍然是一个有问题的解决方案,因为在代码中可能会有其他部分使用文件系统接口而不是相同的文件,并且它可能会干扰它们(即使是在单线程应用程序中)。另外,它不检查null(例如,在getClass(). getresource()中。

This particular Java NIO interface is kind of horrible, since it introduces a global/singleton non thread-safe resource, and its documentation is extremely vague (a lot of unknowns due to provider specific implementations). Results may vary for other FileSystem providers (not JAR). Maybe there's a good reason for it being that way; I don't know, I haven't researched the implementations.

这个特定的Java NIO接口有点可怕,因为它引入了一个全局/单例非线程安全的资源,而且它的文档非常模糊(由于提供者特定的实现,很多未知因素)。结果可能因其他文件系统提供者(而不是JAR)而异。也许这是有原因的;我不知道,我没有研究过这些实现。

#6


4  

Here's a method I wrote for a "run all JUnits under a package". You should be able to adapt it to your needs.

这里有一个方法,我为“在一个包下运行所有junit”编写了一个方法。你应该能够适应你的需要。

private static void findClassesInJar(List<String> classFiles, String path) throws IOException {
    final String[] parts = path.split("\\Q.jar\\\\E");
    if (parts.length == 2) {
        String jarFilename = parts[0] + ".jar";
        String relativePath = parts[1].replace(File.separatorChar, '/');
        JarFile jarFile = new JarFile(jarFilename);
        final Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            final JarEntry entry = entries.nextElement();
            final String entryName = entry.getName();
            if (entryName.startsWith(relativePath)) {
                classFiles.add(entryName.replace('/', File.separatorChar));
            }
        }
    }
}

Edit: Ah, in that case, you might want this snippet as well (same use case :) )

编辑:啊,在这种情况下,您可能需要这段代码(同样的用例:))

private static File findClassesDir(Class<?> clazz) {
    try {
        String path = clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
        final String codeSourcePath = URLDecoder.decode(path, "UTF-8");
        final String thisClassPath = new File(codeSourcePath, clazz.getPackage().getName().repalce('.', File.separatorChar));
    } catch (UnsupportedEncodingException e) {
        throw new AssertionError("impossible", e);
    }
}

#7


3  

A jar file is just a zip file with a structured manifest. You can open the jar file with the usual java zip tools and scan the file contents that way, inflate streams, etc. Then use that in a getResourceAsStream call, and it should be all hunky dory.

jar文件只是一个带有结构化清单的zip文件。您可以使用常用的java zip工具打开jar文件,并通过这样的方式扫描文件内容,然后在getresour流调用中使用它,并且它应该都是很健壮的dory。

EDIT / after clarification

编辑/澄清后

It took me a minute to remember all the bits and pieces and I'm sure there are cleaner ways to do it, but I wanted to see that I wasn't crazy. In my project image.jpg is a file in some part of the main jar file. I get the class loader of the main class (SomeClass is the entry point) and use it to discover the image.jpg resource. Then some stream magic to get it into this ImageInputStream thing and everything is fine.

我花了一分钟的时间来记住所有的零碎东西,我确信有更干净的方法来做这件事,但我想知道我不是疯了。在我的项目图像中,jpg是一个文件在主jar文件的某些部分。我获得了主类的类装入器(SomeClass是入口点),并使用它来发现image.jpg资源。然后一些流魔术把它放到这个ImageInputStream的东西里,一切都很好。

InputStream inputStream = SomeClass.class.getClassLoader().getResourceAsStream("image.jpg");
JPEGImageReaderSpi imageReaderSpi = new JPEGImageReaderSpi();
ImageReader ir = imageReaderSpi.createReaderInstance();
ImageInputStream iis = new MemoryCacheImageInputStream(inputStream);
ir.setInput(iis);
....
ir.read(0); //will hand us a buffered image

#8


3  

Some time ago I made a function that gets classess from inside JAR:

不久前,我做了一个函数,从罐子里面得到classess:

public static Class[] getClasses(String packageName) 
throws ClassNotFoundException{
    ArrayList<Class> classes = new ArrayList<Class> ();

    packageName = packageName.replaceAll("\\." , "/");
    File f = new File(jarName);
    if(f.exists()){
        try{
            JarInputStream jarFile = new JarInputStream(
                    new FileInputStream (jarName));
            JarEntry jarEntry;

            while(true) {
                jarEntry=jarFile.getNextJarEntry ();
                if(jarEntry == null){
                    break;
                }
                if((jarEntry.getName ().startsWith (packageName)) &&
                        (jarEntry.getName ().endsWith (".class")) ) {
                    classes.add(Class.forName(jarEntry.getName().
                            replaceAll("/", "\\.").
                            substring(0, jarEntry.getName().length() - 6)));
                }
            }
        }
        catch( Exception e){
            e.printStackTrace ();
        }
        Class[] classesA = new Class[classes.size()];
        classes.toArray(classesA);
        return classesA;
    }else
        return null;
}

#9


3  

Here is an example of using Reflections library to recursively scan classpath by regex name pattern augmented with a couple of Guava perks to to fetch resources contents:

这里有一个使用反射库的例子,它通过regex名称模式通过regex名称模式来递归地扫描类路径,并通过一些Guava特权来获取资源内容:

Reflections reflections = new Reflections("com.example.package", new ResourcesScanner());
Set<String> paths = reflections.getResources(Pattern.compile(".*\\.template$"));

Map<String, String> templates = new LinkedHashMap<>();
for (String path : paths) {
    log.info("Found " + path);
    String templateName = Files.getNameWithoutExtension(path);
    URL resource = getClass().getClassLoader().getResource(path);
    String text = Resources.toString(resource, StandardCharsets.UTF_8);
    templates.put(templateName, text);
}

This works with both jars and exploded classes.

这与jar和爆炸类一起工作。

#10


2  

Given an actual JAR file, you can list the contents using JarFile.entries(). You will need to know the location of the JAR file though - you can't just ask the classloader to list everything it could get at.

给定一个实际的JAR文件,您可以使用JarFile.entries()来列出内容。但是,您需要知道JAR文件的位置——您不能只要求类加载器列出它能得到的所有内容。

You should be able to work out the location of the JAR file based on the URL returned from ThisClassName.class.getResource("ThisClassName.class"), but it may be a tiny bit fiddly.

您应该能够根据从这个类名返回的URL来计算JAR文件的位置(“ThisClassName.class”),但是它可能有点小麻烦。

#11


1  

There are two very useful utilities both called JarScan:

有两个非常有用的实用工具,都叫JarScan:

  1. www.inetfeedback.com/jarscan

    www.inetfeedback.com/jarscan

  2. jarscan.dev.java.net

    jarscan.dev.java.net

See also this question: JarScan, scan all JAR files in all subfolders for specific class

还可以看到这个问题:JarScan,扫描所有子文件夹中的所有JAR文件,用于特定的类。

#12


0  

Just a different way of listing/reading files from a jar URL and it does it recursively for nested jars

只是从一个jar URL中列出/读取文件的另一种方式,它会递归地用于嵌套的jar。

https://gist.github.com/trung/2cd90faab7f75b3bcbaa

https://gist.github.com/trung/2cd90faab7f75b3bcbaa

URL urlResource = Thead.currentThread().getContextClassLoader().getResource("foo");
JarReader.read(urlResource, new InputStreamCallback() {
    @Override
    public void onFile(String name, InputStream is) throws IOException {
        // got file name and content stream 
    }
});

#13


0  

I've ported acheron55's answer to Java 7 and closed the FileSystem object. This code works in IDE's, in jar files and in a jar inside a war on Tomcat 7; but note that it does not work in a jar inside a war on JBoss 7 (it gives FileSystemNotFoundException: Provider "vfs" not installed, see also this post). Furthermore, like the original code, it is not thread safe, as suggested by errr. For these reasons I have abandoned this solution; however, if you can accept these issues, here is my ready-made code:

我已经将acheron55的答案移植到Java 7并关闭了文件系统对象。这个代码在IDE中工作,在jar文件中工作,在Tomcat 7的战争中,在一个jar中工作;但是请注意,在JBoss 7的war中,它并不是在jar中工作(它提供了文件系统notfoundexception:没有安装的提供者“vfs”,也可以看到这个帖子)。而且,与原始代码一样,它不是线程安全的,正如errr所建议的那样。由于这些原因,我放弃了这个解决方案;但是,如果你能接受这些问题,这是我的现成代码:

import java.io.IOException;
import java.net.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;

public class ResourceWalker {

    public static void main(String[] args) throws URISyntaxException, IOException {
        URI uri = ResourceWalker.class.getResource("/resources").toURI();
        System.out.println("Starting from: " + uri);
        try (FileSystem fileSystem = (uri.getScheme().equals("jar") ? FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()) : null)) {
            Path myPath = Paths.get(uri);
            Files.walkFileTree(myPath, new SimpleFileVisitor<Path>() { 
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    System.out.println(file);
                    return FileVisitResult.CONTINUE;
                }
            });
        }
    }
}

注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:https://www.itdaan.com/blog/2009/09/15/62338ba6d1d0c8662ccff7dad1262b3b.html



 
粤ICP备14056181号  © 2014-2021 ITdaan.com