How to list the files inside a JAR file?
我有这段代码,它从目录中读取所有文件。
1 2 3 4 5 6 7 | 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文件中的所有图像,这样我就可以加载它们:
1 | ImageIO.read(this.getClass().getResource("CompanyLogo.png")); |
(之所以有效,是因为" CompanyLogo"是"硬编码的",但是JAR文件中的图像数量可以是10到200个可变长度。)
编辑
所以我想我的主要问题是:如何知道我的主类所在的JAR文件的名称?
可以使用
我的结构是这样的:
他们就像:
1 2 3 4 5 6 7 8 | 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"
1 | ImageIO.read(this.getClass().getResource("images/image01.png)); |
但是仅由于我知道文件名,其余的我必须动态加载它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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)文件创建一个
适用于IDE和.jar文件的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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()); } } } |
埃里克森的答案很完美:
这是工作代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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() ] ); |
我刚刚从中修改了我的加载方法:
1 2 | File[] webimages = ... BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex].getName() )); |
对此:
1 2 3 | String [] webimages = ... BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex])); |
我想扩展acheron55的答案,因为它是一个非常不安全的解决方案,其原因有以下几种:
这是一个较为安全的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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()); } } |
真正不需要在文件名上进行同步。每次都可以简单地在同一对象上同步(或使方法
我会说这仍然是一个有问题的解决方案,因为代码中可能还有其他部分在同一文件上使用
另外,它不会检查
这个特殊的Java NIO接口很可怕,因为它引入了全局/单个非线程安全资源,并且其文档非常模糊(由于提供程序特定的实现,很多未知数)。对于其他
So I guess my main problem would be, how to know the name of the jar where my main class lives.
blockquote>
假设您的项目打包在一个Jar中(不一定是true!),则可以将ClassLoader.getResource()或findResource()与类名一起使用(后跟.class),以获取包含给定类的jar。您必须从返回的URL解析jar名称(不是那么难),我将留给读者练习:-)
确保测试该类不是jar的一部分的情况。
这是一个使用Reflections库通过正则表达式名称模式以几个Guava特权增强的递归方式扫描类路径的示例,以获取资源内容:
1
2
3
4
5
6
7
8
9
10
11 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);
}这适用于jar和爆炸类。
这是我为"在一个程序包下运行所有??JUnit"编写的一种方法。您应该能够使其适应您的需求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 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));
}
}
}
}编辑:
嗯,在这种情况下,您可能也需要此代码段(相同的用例:))
1
2
3
4
5
6
7
8
9 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);
}
}
我已经将acheron55的答案移植到Java 7并关闭了
FileSystem 对象。该代码可在IDE,jar文件和Tomcat 7战争中的jar中工作;但请注意,在JBoss 7的战争中,它不能在jar中使用(它提供FileSystemNotFoundException: Provider"vfs" not installed ,另请参见此文章)。此外,就像原始代码一样,它也不是线程安全的,如errr所建议。由于这些原因,我放弃了该解决方案。但是,如果您可以接受这些问题,这是我现成的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 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;
}
});
}
}
}
前段时间,我做了一个从JAR内部获取类的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 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;
}
给定一个实际的JAR文件,您可以使用
JarFile.entries() 列出内容。但是,您将需要知道JAR文件的位置-您不能只要求类加载器列出它可以得到的所有内容。您应该能够根据从
ThisClassName.class.getResource("ThisClassName.class") 返回的URL来计算JAR文件的位置,但是这可能有点麻烦。
jar文件只是具有结构化清单的zip文件。您可以使用常规的Java zip工具打开jar文件,然后以这种方式扫描文件内容,对流进行充气,等等。然后在getResourceAsStream调用中使用该文件,并且应该全部使用。
编辑/澄清后
我花了一分钟的时间来记住所有的点点滴滴,我敢肯定有更干净的方法可以做到这一点,但我想知道自己并不疯狂。在我的项目中,image.jpg是主jar文件中某些部分的文件。我得到了主类的类加载器(SomeClass是入口点),并使用它来发现image.jpg资源。然后,使用一些流魔术将其放入ImageInputStream中,一切都很好。
1
2
3
4
5
6
7 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
列出类路径中所有资源的最健壮的机制当前是将这种模式与ClassGraph一起使用,因为它可以处理最广泛的类路径规范机制,包括新的JPMS模块系统。 (我是ClassGraph的作者。)
How to know the name of the JAR file where my main class lives?
1
2
3
4
5
6 URI mainClasspathElementURI;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
.enableClassInfo().scan()) {
mainClasspathElementURI =
scanResult.getClassInfo("x.y.z.MainClass").getClasspathElementURI();
}How can I read the contents of a directory in a similar fashion within a JAR file?
1
2
3
4
5 List<String> classpathElementResourcePaths;
try (ScanResult scanResult = new ClassGraph().overrideClasspath(mainClasspathElementURI)
.scan()) {
classpathElementResourcePaths = scanResult.getAllResources().getPaths();
}还有许多其他方法来处理资源。
1
2
3
4
5
6
7
8
9
10
11 public static ArrayList<String> listItems(String path) throws Exception{
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(path);
byte[] b = new byte[in.available()];
in.read(b);
String data = new String(b);
String[] s = data.split("
");
List<String> a = Arrays.asList(s);
ArrayList<String> m = new ArrayList<>(a);
return m;
}
有两个非常有用的实用程序,都称为JarScan:
www.inetfeedback.com/jarscan
jarscan.dev.java.net
另请参阅以下问题:JarScan,扫描所有子文件夹中的所有JAR文件以查找特定的类
只是从jar URL列出/读取文件的一种不同方法,它对嵌套jar进行递归处理
https://gist.github.com/trung/2cd90faab7f75b3bcbaa
1
2
3
4
5
6
7 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
}
});