我有这段代码,它从目录中读取所有文件。
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));
但仅仅因为我知道文件名,其余的我必须动态加载它们。
答案 0 :(得分:83)
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和“爆炸”目录的代码。
答案 1 :(得分:63)
适用于IDE&.jar和.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());
}
}
}
答案 2 :(得分:20)
erickson的answer完美地运作:
这是工作代码。
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]));
答案 3 :(得分:8)
我想扩展acheron55&#39; answer,因为它是一种非常安全的解决方案,原因如下:
FileSystem
对象。FileSystem
对象是否已存在。这是一个更安全的解决方案:
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)的结果可能会有所不同。也许有这样一个很好的理由;我不知道,我还没有研究过这些实现。
答案 4 :(得分: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));
}
}
}
}
编辑: 啊,在这种情况下,您可能也想要这个片段(相同的用例:))
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);
}
}
答案 5 :(得分:5)
所以我想我的主要问题是,如何知道我的主班所在的罐子的名称。
假设你的项目被打包在一个Jar中(不一定是真的!),你可以使用ClassLoader.getResource()或findResource()和类名(后跟.class)来获取包含给定类的jar 。你将不得不从返回的URL解析jar名称(不是那么难),我将把它作为练习留给读者: - )
请务必测试该类不属于jar的情况。
答案 6 :(得分:3)
给定一个实际的JAR文件,您可以使用JarFile.entries()
列出内容。您需要知道JAR文件的位置 - 您不能只要求类加载器列出它可以获得的所有内容。
您应该能够根据ThisClassName.class.getResource("ThisClassName.class")
返回的URL计算出JAR文件的位置,但它可能有点过时。
答案 7 :(得分:3)
我已将acheron55's answer移植到Java 7并关闭FileSystem
对象。这段代码适用于IDE,jar文件以及Tomcat 7战争中的jar;但请注意,它在JBoss 7上的战争中的FileSystemNotFoundException: Provider "vfs" not installed
,另见this post)。此外,与原始代码一样,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;
}
});
}
}
}
答案 8 :(得分:3)
这是一个使用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);
}
这适用于罐子和爆炸类。
答案 9 :(得分:3)
前段时间我创建了一个从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;
}
答案 10 :(得分:3)
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
答案 11 :(得分:1)
有两个非常有用的实用工具,名为JarScan:
答案 12 :(得分:1)
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("\n");
List<String> a = Arrays.asList(s);
ArrayList<String> m = new ArrayList<>(a);
return m;
}
答案 13 :(得分:1)
由于它使用通配符,因此在匹配特定文件名时更加灵活。在功能样式中,可能类似于:
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
import static java.nio.file.FileSystems.getDefault;
import static java.nio.file.FileSystems.newFileSystem;
import static java.util.Collections.emptyMap;
/**
* Responsible for finding file resources.
*/
public class ResourceWalker {
/**
* Globbing pattern to match font names.
*/
public static final String GLOB_FONTS = "**.{ttf,otf}";
/**
* @param directory The root directory to scan for files matching the glob.
* @param c The consumer function to call for each matching path
* found.
* @throws URISyntaxException Could not convert the resource to a URI.
* @throws IOException Could not walk the tree.
*/
public static void walk(
final String directory, final String glob, final Consumer<Path> c )
throws URISyntaxException, IOException {
final var resource = ResourceWalker.class.getResource( directory );
final var matcher = getDefault().getPathMatcher( "glob:" + glob );
if( resource != null ) {
final var uri = resource.toURI();
final var jar = uri.getScheme().equals( "jar" );
final var path = jar ? toFileSystem( uri, directory ) : Paths.get( uri );
try( final var walk = Files.walk( path, 10 ) ) {
for( final var it = walk.iterator(); it.hasNext(); ) {
final Path p = it.next();
if( matcher.matches( p ) ) {
c.accept( p );
}
}
}
}
}
private static Path toFileSystem( final URI uri, final String directory )
throws IOException {
try( final var fs = newFileSystem( uri, emptyMap() ) ) {
return fs.getPath( directory );
}
}
}
考虑对文件扩展名进行参数设置,使读者不容易进行练习。
请注意Files.walk
。根据文档:
必须在try-with-resources语句或类似的控制结构中使用此方法,以确保在流的操作完成后立即关闭流的打开目录。
同样,newFileSystem
必须关闭。
答案 14 :(得分:0)
从jar URL列出/读取文件的另一种方式是嵌套jar的递归方式
https://gist.github.com/trung/2cd90faab7f75b3bcbaa
- (IBAction)presentScene:(id)sender {
NSString *viewControllerID = @"myScene";
UIStoryboard *storyboard = self.storyboard;
MyViewController *controller = (MyViewController *)[storyboard instantiateViewControllerWithIdentifier:viewControllerID];
[self.navigationController pushViewController: controller animated:YES]
}
答案 15 :(得分:0)
当前列出类路径中所有资源的最健壮的机制是to use this pattern with ClassGraph,因为它处理widest possible array of classpath specification mechanisms,包括新的JPMS模块系统。 (我是ClassGraph的作者。)
如何知道我的主要班级居住的JAR文件的名称?
URI mainClasspathElementURI;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
.enableClassInfo().scan()) {
mainClasspathElementURI =
scanResult.getClassInfo("x.y.z.MainClass").getClasspathElementURI();
}
如何在JAR文件中以类似方式读取目录的内容?
List<String> classpathElementResourcePaths;
try (ScanResult scanResult = new ClassGraph().overrideClasspath(mainClasspathElementURI)
.scan()) {
classpathElementResourcePaths = scanResult.getAllResources().getPaths();
}
答案 16 :(得分:0)
顺便提一下,如果您已经在使用 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()));
}