How to list the files inside a JAR file?
У меня есть этот код, который читает все файлы из каталога.
File textFolder = new File("text_directory");
File [] texFiles = textFolder.listFiles( new FileFilter() {
public boolean accept( File file ) {
return file.getName().endsWith(".txt");
}
});
Работает отлично. Он заполняет массив всеми файлами, которые заканчиваются на ".txt" из каталога "text_directory".
Как я могу прочитать содержимое каталога аналогичным образом в файле 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));
Но только потому, что я знаю имя файла, в остальном я должен загружать их динамически.
17 ответов
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 вы можете создать FileSystem
из файла JAR (zip), а затем используйте механизмы обхода и фильтрации каталогов 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());
}
}
}
Ответ Эриксона сработал отлично:
Вот рабочий код.
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, поскольку это очень небезопасное решение по нескольким причинам:
- Это не закрывает
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
s (например, на getClass().getResource()
,
Этот конкретный интерфейс Java NIO выглядит ужасно, поскольку он представляет глобальный / одноэлементный ресурс, не поддерживающий потоки, и его документация чрезвычайно расплывчата (много неизвестных из-за реализаций, специфичных для провайдера). Результаты могут отличаться для других FileSystem
провайдеры (не JAR). Может быть, для этого есть веская причина; Я не знаю, я не исследовал реализации.
Поэтому я думаю, что моей главной проблемой было бы, как узнать название банки, где живет мой основной класс.
Предполагая, что ваш проект упакован в Jar (не обязательно true!), Вы можете использовать ClassLoader.getResource () или findResource () с именем класса (за которым следует.class), чтобы получить jar, содержащий данный класс. Вам нужно будет проанализировать имя фляги по возвращаемому URL (не так уж сложно), которое я оставлю в качестве упражнения для читателя:-)
Обязательно протестируйте на случай, когда класс не является частью кувшина.
Я перенес acheron55 ответ на Java 7 и закрыл FileSystem
объект. Этот код работает в IDE, в файлах jar и jar во время войны с Tomcat 7; но обратите внимание, что он не работает в банке во время войны на 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;
}
});
}
}
}
Просто упомяну, что если вы уже используете Spring, вы можете воспользоваться преимуществами
PathMatchingResourcePatternResolver
.
Например, чтобы получить все файлы PNG из
images
папка в ресурсах
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()));
}
Вот пример использования библиотеки 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);
}
Это работает как с банками, так и с взорванными классами.
Вот метод, который я написал для "запуска всех 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);
}
}
Некоторое время назад я сделал функцию, которая получает класс изнутри 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, вы можете перечислить содержимое с помощью JarFile.entries()
, Вам нужно будет знать местоположение файла JAR - вы не можете просто попросить загрузчик классов перечислить все, что он может получить.
Вы должны быть в состоянии определить местоположение файла JAR на основе URL, возвращенного из ThisClassName.class.getResource("ThisClassName.class")
, но это может быть немного неудобно.
Файл jar - это просто zip-файл со структурированным манифестом. Вы можете открыть файл jar с помощью обычных инструментов java zip и таким образом сканировать содержимое файла, раздувать потоки и т. Д. Затем использовать это в вызове getResourceAsStream, и все должно быть просто напыщенно.
РЕДАКТИРОВАТЬ / после уточнения
Мне потребовалась минута, чтобы вспомнить все кусочки, и я уверен, что есть более чистые способы сделать это, но я хотел видеть, что я не сумасшедший. В моем проекте 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
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;
}
Еще один в дорогу:
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import static java.nio.file.FileSystems.newFileSystem;
import static java.util.Collections.emptyMap;
public class ResourceWalker {
private static final PathMatcher FILE_MATCHER =
FileSystems.getDefault().getPathMatcher( "glob:**.ttf" );
public static List<Path> walk( final String directory )
throws URISyntaxException, IOException {
final List<Path> filenames = new ArrayList<>();
final var resource = ResourceWalker.class.getResource( directory );
if( resource != null ) {
final var uri = resource.toURI();
final var path = uri.getScheme().equals( "jar" )
? newFileSystem( uri, emptyMap() ).getPath( directory )
: Paths.get( uri );
final var walk = Files.walk( path, 10 );
for( final var it = walk.iterator(); it.hasNext(); ) {
final Path p = it.next();
if( FILE_MATCHER.matches( p ) ) {
filenames.add( p );
}
}
}
return filenames;
}
}
Это немного более гибко для сопоставления определенных имен файлов, поскольку использует подстановочные знаки.
Более функциональный стиль:
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.function.Consumer;
import static java.nio.file.FileSystems.newFileSystem;
import static java.util.Collections.emptyMap;
/**
* Responsible for finding file resources.
*/
public class ResourceWalker {
private static final PathMatcher FILE_MATCHER =
FileSystems.getDefault().getPathMatcher( "glob:**.ttf" );
public static void walk( final String dirName, final Consumer<Path> f )
throws URISyntaxException, IOException {
final var resource = ResourceWalker.class.getResource( dirName );
if( resource != null ) {
final var uri = resource.toURI();
final var path = uri.getScheme().equals( "jar" )
? newFileSystem( uri, emptyMap() ).getPath( dirName )
: Paths.get( uri );
final var walk = Files.walk( path, 10 );
for( final var it = walk.iterator(); it.hasNext(); ) {
final Path p = it.next();
if( FILE_MATCHER.matches( p ) ) {
f.accept( p );
}
}
}
}
}
Наиболее надежным механизмом для перечисления всех ресурсов в пути к классам в настоящее время является использование этого шаблона с ClassGraph, поскольку он обрабатывает максимально широкий спектр механизмов спецификации пути к классам, включая новую модульную систему 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();
}
Есть две очень полезные утилиты, которые называются JarScan:
Смотрите также этот вопрос: JarScan, сканируйте все файлы JAR во всех подпапках для определенного класса
Просто другой способ перечисления / чтения файлов с URL-адреса jar, и он делает это рекурсивно для вложенных 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
}
});