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

Osc*_*Ryz 107 java jar file getresource java-io

我有这个代码从目录中读取所有文件.

    File textFolder = new File("text_directory");

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

它很棒.它使用目录"text_directory"中以".txt"结尾的所有文件填充数组.

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

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

ImageIO.read(this.getClass().getResource("CompanyLogo.png"));
Run Code Online (Sandbox Code Playgroud)

(那个是有效的,因为"CompanyLogo"是"硬编码的",但JAR文件中的图像数量可以是10到200个可变长度.)

编辑

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

当然,我可以使用它来阅读它java.util.Zip.

我的结构是这样的:

他们就像:

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 
Run Code Online (Sandbox Code Playgroud)

现在我可以使用以下命令加载例如"images/image01.png":

    ImageIO.read(this.getClass().getResource("images/image01.png));
Run Code Online (Sandbox Code Playgroud)

但只是因为我知道文件名,其余的我必须动态加载它们.

eri*_*son 84

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... */
}
Run Code Online (Sandbox Code Playgroud)

请注意,在Java 7中,您可以FileSystem从JAR(zip)文件创建一个,然后使用NIO的目录行走和过滤机制来搜索它.这样可以更轻松地编写处理JAR和"爆炸"目录的代码.

  • 是的,如果我们想列出这个jar文件中的所有条目,这段代码就可以了.但是,如果我只想在jar中列出一个子目录,例如**example.jar/dir1/dir2/**,我怎样才能直接列出该子目录中的所有文件?或者我需要解压这个jar文件?我非常感谢你的帮助! (9认同)
  • @Vadzim - 我想你的意思是链接http://stackoverflow.com/a/5194049/680925. (2认同)

ach*_*n55 68

适用于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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 别忘了关闭`fileSystem`! (6认同)
  • `FileSystems.newFileSystem()`接受一个`Map <String,?>`,所以你需要指定它需要返回一个适当类型的`Collections.emptyMap()`.这适用:`Collections.<String,Object> emptyMap()`. (5认同)
  • 神奇!但是URI uri = MyClass.class.getResource("/ resources").toURI(); 应该有MyClass.class.getClassLoader().getResource("/ resources").toURI(); 即getClassLoader().否则它不适合我. (4认同)
  • `myPath = fileSystem.getPath("/ resources");`对我不起作用; 它找不到任何东西.它应该是我的情况下的"图像"和"图像" - 目录肯定包含在我的罐子里! (4认同)
  • 这应该是 1.8 的第一个答案(`Files` 中的 `walk` 方法仅在 1.8 中可用)。唯一的问题是资源目录出现在`Files.walk(myPath, 1)`中,而不仅仅是文件。我想第一个元素可以简单地忽略 (3认同)

Osc*_*Ryz 20

埃里克森的答案 非常有效:

这是工作代码.

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() ] );
Run Code Online (Sandbox Code Playgroud)

我刚从这里修改了我的加载方法:

File[] webimages = ... 
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex].getName() ));
Run Code Online (Sandbox Code Playgroud)

对此:

String  [] webimages = ...

BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));
Run Code Online (Sandbox Code Playgroud)


Eya*_*oth 8

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

  1. 它不会关闭FileSystem对象.
  2. 它不检查FileSystem对象是否已存在.
  3. 它不是线程安全的.

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

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());
    }
}   
Run Code Online (Sandbox Code Playgroud)

没有必要同步文件名; 一个人可以简单地每次同步同一个对象(或制作方法synchronized),这纯粹是一种优化.

我会说这仍然是一个有问题的解决方案,因为代码中可能有其他部分使用FileSystem相同文件的接口,并且它可能会干扰它们(即使在单线程应用程序中).
此外,它不会检查nulls(例如,打开getClass().getResource().

这个特殊的Java NIO接口有点可怕,因为它引入了一个全局/单一的非线程安全资源,其文档非常模糊(由于提供程序特定的实现,很多未知数).其他FileSystem提供者(不是JAR)的结果可能会有所不同.也许有这样一个很好的理由; 我不知道,我还没有研究过这些实现.

  • 外部资源(例如 FS)的同步在一台虚拟机内没有多大意义。可能有其他应用程序在您的虚拟机外部访问它。此外,即使在您自己的应用程序内部,基于文件名的锁定也可以轻松绕过。有了这个东西,最好依靠操作系统同步机制,比如文件锁定。 (2认同)

Pin*_*ino 8

我已将acheron55 的答案移植到 Java 7 并关闭了该FileSystem对象。这段代码适用于 IDE、jar 文件和 Tomcat 7 战争中的 jar;但要注意,它并没有在一个罐子里一战中在JBoss 7工作(它给人FileSystemNotFoundException: Provider "vfs" not installed,也看到这个帖子)。此外,就像原始代码一样,它不是线程安全的,正如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;
                }
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Kra*_*iss 7

只是提一下,如果您已经在使用 Spring,则可以利用PathMatchingResourcePatternResolver.

images例如从资源文件夹中获取所有PNG文件

ClassLoader cl = this.getClass().getClassLoader(); 
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(cl);
Resource[] resources = resolver.getResources("images/*.png");
for (Resource r: resources){
    logger.info(r.getFilename());
    // From your example
    // ImageIO.read(cl.getResource("images/" + r.getFilename()));
}
Run Code Online (Sandbox Code Playgroud)


Kev*_*Day 6

所以我想我的主要问题是,如何知道我的主要课程所在的jar的名称.

假设您的项目打包在Jar中(不一定是真的!),您可以使用ClassLoader.getResource()或findResource()与类名(后跟.class)来获取包含给定类的jar.你将不得不从返回的URL解析jar名称(不是那么难),我将把它作为练习留给读者:-)

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


Ran*_*ron 5

这是我为"在包下运行所有​​JUnits"编写的方法.您应该能够根据您的需求进行调整.

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));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:啊,在这种情况下,你可能也想要这个片段(相同的用例:))

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);
    }
}
Run Code Online (Sandbox Code Playgroud)


Vad*_*zim 5

这是一个使用Reflections库通过正则表达式名称模式递归扫描类路径的示例,并增加了一些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);
}
Run Code Online (Sandbox Code Playgroud)

这适用于 jars 和爆炸类。


归档时间:

查看次数:

125823 次

最近记录:

6 年 前