我有这段代码可以从目录中读取所有文件。

    File textFolder = new File("text_directory");

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

效果很好。它使用目录“text_directory”中以“.txt”结尾的所有文件填充数组。

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

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

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

(该方法之所以有效,是因为“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 

现在我可以使用以下方法加载例如“images/image01.png”:

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

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

有帮助吗?

解决方案

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... */
}

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

其他提示

适用于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());
        }
    }
}

erickson的答案完美运作:

这是工作代码。

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() ] );

我刚刚修改了我的加载方法:

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

对此:

String  [] webimages = ...

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

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

  1. 它不会关闭 FileSystem 对象。
  2. 它不检查 FileSystem 对象是否已存在。
  3. 它不是线程安全的。
  4. 这是一个更安全的解决方案:

    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());
        }
    }   
    

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

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

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

这是我为“运行包下的所有 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));
            }
        }
    }
}

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

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);
    }
}
  
    

所以我想我的主要问题是,如何知道我的主班所在的罐子的名称。

  

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

请务必测试该类不属于jar的情况。

jar文件只是一个带有结构化清单的zip文件。您可以使用通常的java zip工具打开jar文件,然后扫描文件内容,膨胀流等。然后在getResourceAsStream调用中使用它,它应该都是hunky dory。

编辑/澄清后

我花了一分钟记住所有的点点滴滴,我确信有更清洁的方法可以做到,但我想看到我并不疯狂。在我的项目中,image.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

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

您应该能够根据从 ThisClassName.class.getResource(&quot; ThisClassName.class&quot;)返回的URL计算出JAR文件的位置,但它可能只是一点点繁琐。

前段时间我创建了一个从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;
}

以下是使用 Reflections 库通过正则表达式名称模式递增扫描类路径的示例 Guava 有权获取资源内容:

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

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);
}

这适用于罐子和爆炸类。

我移植了 acheron55的回答 Java 7并关闭 FileSystem 对象。此代码适用于IDE,jar文件和Tomcat 7中的战争中的jar;但请注意,它在JBoss 7上的战争中的工作(它提供 FileSystemNotFoundException:Provider&quot; vfs&quot; 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;
                }
            });
        }
    }
}

有两个非常有用的实用工具,名为JarScan:

  1. www.inetfeedback.com/jarscan

  2. jarscan.dev.java.net

  3. 另请参阅此问题: JarScan,扫描特定类的所有子文件夹中的所有JAR文件

从jar URL列出/读取文件的另一种方式是嵌套jar的递归方式

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 
    }
});
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top