Вопрос-ответ

How to list the files inside a JAR file?

Как составить список файлов внутри JAR-файла?

У меня есть этот код, который считывает все файлы из каталога.

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

Но только потому, что я знаю имя файла, в остальном мне приходится загружать их динамически.

Переведено автоматически
Ответ 1
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-файлы и "разнесенные" каталоги.

Ответ 2

Код, который работает как для 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());
}
}
}
Ответ 3

ответ Эриксона сработал отлично:

Вот рабочий код.

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]));
Ответ 4

Я хотел бы подробнее остановиться на ответе acheron55, поскольку это очень небезопасное решение по нескольким причинам:


  1. Это не закрывает FileSystem объект.

  2. Он не проверяет, существует ли уже FileSystem объект.

  3. Это не потокобезопасно.

Это в некоторой степени более безопасное решение:

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 интерфейс поверх тех же файлов, и это может помешать им (даже в однопоточном приложении).
Кроме того, он не проверяет наличие nulls (например, на getClass().getResource().

Этот конкретный интерфейс Java NIO в некотором роде ужасен, поскольку он вводит глобальный / одноэлементный ресурс, не являющийся потокобезопасным, а его документация крайне расплывчата (много неизвестного из-за специфичных для поставщика реализаций). Результаты могут отличаться у других FileSystem провайдеров (не JAR). Возможно, для этого есть веская причина; я не знаю, я не исследовал реализации.

java file jar