如何列出JAR文件中的文件?
我有这个代码读取目录中的所有文件。
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!http://img.dovov.comimage01.png my.jar!http://img.dovov.comimage02a.png my.jar!http://img.dovov.comimwge034.png my.jar!http://img.dovov.comimagAe01q.png my.jar!/META-INF/manifest
现在我可以加载例如“images / image01.png”使用:
ImageIO.read(this.getClass().getResource("images/image01.png));
但只是因为我知道文件名,其余的我必须dynamic加载它们。
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)文件创build一个FileSystem
,然后使用NIO的目录遍历和过滤机制来search它。 这将使编写处理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()); } } }
埃里克森的答案完美的工作:
这是工作代码。
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]));
所以我想我的主要问题是,如何知道我的主要类的jar的名字。
假设你的项目打包在一个Jar中(不一定是正确的),你可以使用ClassLoader.getResource()或findResource()以及类名(后跟.class)来获取包含给定类的jar。 你将不得不从被返回的URLparsingjar名称(不是那么难),我将作为练习离开阅读器:-)
请务必testing该类不属于jar的情况。
我想扩大acheron55的答案 ,因为这是一个非常不安全的解决scheme,原因如下:
- 它不closures
FileSystem
对象。 - 它不检查
FileSystem
对象是否已经存在。 - 这不是线程安全的。
这是一个更安全的解决scheme:
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
),这纯粹是一种优化。
我会说这仍然是一个有问题的解决scheme,因为代码中可能有其他部分使用FileSystem
接口来覆盖相同的文件,并且可能会干扰它们(即使在单个线程应用程序中)。
此外,它不检查null
s(例如,在getClass().getResource()
。
这个特定的Java NIO接口有点可怕,因为它引入了一个全局/单例非线程安全的资源,其文档非常含糊(由于提供者的具体实现,很多未知)。 其他文件系统提供者(不是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里面得到分类的函数:
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文件只是一个带有结构化清单的zip文件。 你可以用通常的java压缩工具打开jar文件,然后扫描文件内容,膨胀stream等,然后在getResourceAsStream调用中使用它,它应该是所有的。
编辑/澄清后
我花了一分钟记住所有的零零碎碎的东西,我相信有更干净的方法去做,但是我想看到我没有疯狂。 在我的项目image.jpg是在主jar文件的一部分的文件。 我得到了主类的类加载器(SomeClass是入口点),并使用它来发现image.jpg资源。 然后一些stream魔法将它放入这个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
下面是一个使用reflection库recursion扫描类path的例子,通过正则expression式名称模式增加了几个番石榴特权来获取资源内容:
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子和爆炸类。
给定一个实际的JAR文件,可以使用JarFile.entries()
列出内容。 您将需要知道JAR文件的位置,但不能只是要求类加载器列出所有可能的内容。
您应该能够根据从ThisClassName.class.getResource("ThisClassName.class")
返回的URL来ThisClassName.class.getResource("ThisClassName.class")
JAR文件的位置,但这可能有点儿烦人。
有两个非常有用的实用程序,都称为JarScan:
-
jarscan.dev.java.net
另请参阅此问题: JarScan扫描所有子文件夹中的所有JAR文件以查找特定的类
只是一个不同的方式来从jarurl上载/读取文件,并且它嵌套的jar是recursion的
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 } });
我已经将acheron55的答案移植到Java 7并closures了FileSystem
对象。 这个代码可以在IDE中,在jar文件中,在Tomcat 7中的war中运行。 但请注意,它不能在JBoss 7上的战争中的jar(它给FileSystemNotFoundException: Provider "vfs" not installed
,请参阅此帖 )。 此外,像原始代码一样,它不是线程安全的,正如errr所build议的那样。 由于这些原因,我放弃了这个解决scheme。 不过,如果你能接受这些问题,这里是我现成的代码:
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; } }); } } }