JARファイル内のファイルをリストする方法は?


114

ディレクトリからすべてのファイルを読み取るこのコードがあります。

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

しかし、ファイル名がわかっているという理由だけで、残りのファイルは動的にロードする必要があります。


ただ考えてみてください-zip / jar画像を別のファイルに入れて、別のjarのクラスからそのファイルのエントリを読み取ってみませんか?
Vineet Reynolds

3
配布/インストールには「追加の」手順が必要になるためです。:(ご存知のように、エンドユーザー
。– OscarRyz

jarを作成した場合、トリックを実行するのではなく、jar内にファイルのリストを含めることもできます。
トム・ホーティン-タックライン2009

まあ、私は間違っているかもしれませんが、jarは他のjarの中に埋め込むことができます。one-jar(TM)パッケージソリューションibm.com/developerworks/java/library/j-onejarは、これに基づいて機能します。例外として、あなたの場合、クラスをロードする能力は必要ありません。
Vineet Reynolds

回答:


91
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および「分解された」ディレクトリを処理するコードを簡単に記述できます。


ちょっとありがとう...これを数時間行う方法を今探しています!!
ニュートピア

9
はい、このjarファイル内のすべてのエントリをリストしたい場合、このコードは機能します。しかし、jar内のサブディレクトリ、たとえばexample.jar / dir1 / dir2 /を一覧表示したいだけの場合、このサブディレクトリ内のすべてのファイルを直接一覧表示するにはどうすればよいですか?または、このjarファイルを解凍する必要がありますか?よろしくお願いします!
Ensom Hodder 2012

言及されたJava 7のアプローチは@ acheron55の回答に記載されています
Vadzim 2015

@Vadzim acheron55の答えはJava 7に対するものですか?Java 7ではFiles.walk()またはjava.util.Streamが見つかりませんでしたが、Java 8 ではdocs.oracle.com/javase/8/docs/api/java/nio/file/Files.html
Bruce日

@ BruceSun、Java 7 では、代わりにFiles.walkFileTree(...)を使用できます。
Vadzim、2016年

80

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

5
FileSystems.newFileSystem()を受け取るMap<String, ?>ためCollections.emptyMap()、適切に型指定されたものを返す必要があることを指定する必要があります。これは機能しますCollections.<String, Object>emptyMap()
Zero3

6
素晴らしい!!! しかし、URI uri = MyClass.class.getResource( "/ resources")。toURI(); MyClass.class.getClassLoader()。getResource( "/ resources")。toURI();が必要です。つまり、getClassLoader()です。そうでなければそれは私のために働いていませんでした。
EMM

8
閉じることをお忘れなくfileSystem
gmjonker 2016年

3
これが1.8の最初の答えになるはずです(walkメソッドin Filesは1.8でのみ使用可能です)。唯一の問題はFiles.walk(myPath, 1)、ファイルだけでなく、リソースディレクトリがに表示されることです。最初の要素は単に無視できると思います
toto_tico

4
myPath = fileSystem.getPath("/resources");私にはうまくいきません。何も見つかりません。私の場合は「イメージ」である必要があり、「イメージ」ディレクトリは間違いなく私のjarファイルに含まれています。
phip1611 2018年

21

エリクソンの答え は完璧に機能しました:

これが実際のコードです。

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

9

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 以外)では結果が異なる場合があります。たぶん、それがそのようになっていることには十分な理由があります。わかりません。実装については調査していません。


1
FSなどの外部リソースの同期は、1つのVM内ではあまり意味がありません。VMの外部でそれにアクセスする他のアプリケーションが存在する可能性があります。あなた自身のアプリケーションの中でさえ、ファイル名に基づくあなたのロックは簡単にバイパスすることができます。このため、ファイルロックなどのOS同期メカニズムに依存することをお勧めします。
エスピノサ2017

@Espinosaファイル名ロックメカニズムは完全にバイパスされる可能性があります。私の答えも安全ではありませんが、最小限の労力でJava NIOを使用して得ることができる最も多くのことだと思います。ロックを管理するためにOSに依存すること、またはどのアプリケーションがどのファイルにアクセスするかを制御できないことは、私が仮装ベースのアプリ(テキストエディターなど)を構築している場合を除き、悪い習慣です。自分でロックを管理しないと、例外がスローされるか、スレッドがアプリケーションをブロックします。どちらも回避する必要があります。
Eyal Roth

8

したがって、私の主な問題は、私のメインクラスが存在するjarファイルの名前をどのようにして知るかだと思います。

プロジェクトがJarにパックされている(必ずしもtrueであるとは限りません!)返されたURLからjar名を解析する必要があります(それほど難しくありません)。これは、読者のための演習として残します:-)

クラスがjarの一部ではない場合を必ずテストしてください。


1
ええと-これはコメントなしで控えめにされていたのは興味深いことです...私たちは常に上記の手法を使用しており、うまく機能します。
ケビンの日、

古い問題ですが、私にはこれは素晴らしいハックのようです。ゼロに賛成投票:)
Tuukka Mustonen

これは、クラスにがない場合にここにリストされている唯一のソリューションであるため、賛成CodeSourceです。
モニカ2331977を2013年

7

acheron55の回答をJava 7に移植し、FileSystemオブジェクトを閉じました。このコードは、IDE、jarファイル、Tomcat 7のwar内のjarで機能します。ただし、JBoss 7のwar内のjar では機能しないことに注意してFileSystemNotFoundException: Provider "vfs" not installedください(これは、この投稿も参照してください)。さらに、元のコードと同様に、errrrで示唆されているように、スレッドセーフではありません。これらの理由により、私はこのソリューションを放棄しました。ただし、これらの問題を受け入れることができる場合は、以下に既製のコードを示します。

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

5

「パッケージの下ですべての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);
    }
}

1
大きな問題は、最初にjarファイル名を知ることだと思います。Main-Class:が存在するjarファイルです。
OscarRyz

5

リフレクションライブラリを使用して、リソースの内容を取得するためにいくつかの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);
}

これは、jarと分解クラスの両方で機能します。


リフレクションはまだJava 9以降をサポートしていないことに注意してください:github.com/ronmamo/reflections/issues/186。そこには競合するライブラリへのリンクがあります。
Vadzim

3

jarファイルは、構造化されたマニフェストを含む単なるzipファイルです。通常のjava zipツールを使用してjarファイルを開き、その方法でファイルの内容をスキャンし、ストリームを膨らませることができます。次に、それをgetResourceAsStream呼び出しで使用すると、すべてがおかしくなります。

編集/明確化後

すべての要素を思い出すのに1分かかり、それを行うためのより明確な方法があると確信していますが、私は狂っていないことを確認したかったのです。私のプロジェクトでは、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

このjarには、メインプログラムとリソースが含まれています。セルフジャーを参照するにはどうすればよいですか?jarファイル内から?
OscarRyz

JARファイルを参照するには、文字列として「blah.JAR」を使用します。new File("blah.JAR")たとえば、JARを表すFileオブジェクトを作成するために使用できます。"blah.JAR"をJARの名前に置き換えてください。
Thomas Owens

すでに使い果たしているのと同じjarである場合、クラスローダーはjar内の内容を表示できるはずです...最初に何をしようとしていたのかを誤解しました。
Mikeb 2009

2
ええ、私はすでにそれを持っています、問題は次のようなものが必要なときです: "... getResourceAsStream(" *。jpg "); ..."つまり、動的に、含まれているファイルをリストします。
OscarRyz 2009

3

実際のJARファイルがあれば、を使用して内容をリストできますJarFile.entries()。ただし、JARファイルの場所を知っておく必要があります。クラスローダーに取得可能なすべてのリストを要求することはできません。

から返されたURLに基​​づいてJARファイルの場所を特定できるはずですThisClassName.class.getResource("ThisClassName.class")が、少し面倒かもしれません。


あなたの答えを読んで、別の質問が出されました。呼び出しの結果は次のようになります:this.getClass()。getResource( "/ my_directory"); それは順番に....ディレクトリとして使用できるURLを返す必要がありますか?なぁ…試してみます。
OscarRyz

JARの場所は常に知っています。「。」内にあります。JARの名前が何かであることがわかっている限り、どこかに文字列定数を使用できます。さて、もし人々がJARの名前を変えに行くなら...
トーマス・オーエンス

@Thomas:現在のディレクトリからアプリを実行していると想定しています。「java -jar foo / bar / baz.jar」の何が問題になっていますか?
Jon Skeet

私は、Jarにであるコードがあったnew File("baz.jar)場合、FileオブジェクトはJARファイルを表すと考えます(検証する必要があります)。
Thomas Owens

@トーマス:私はそうは思わない。それはプロセスの現在の作業ディレクトリに関連していると思います。私もチェックする必要があります:)
Jon Skeet

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

2
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;
}

3
このコードスニペットは問題を解決する可能性がありますが、なぜまたはどのように質問に答えるかは説明していません。コードの説明を含めてください。これ投稿の品質を向上させるのに役立ちます。あなたは将来の読者のための質問に答えていることを覚えておいてください、そしてそれらの人々はあなたのコード提案の理由を知らないかもしれません。
Samuel Philipp

jarファイルからコードを実行すると、データは空になります。
Aguid


1

クラスパス内のすべてのリソースを一覧表示するための最も堅牢なメカニズムは、新しいJPMSモジュールシステムを含むクラスパス指定メカニズムの可能な限り幅広い配列を処理するため、現在ClassGraphこのパターンを使用することです。(私は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();
}

リソースを処理する方法は他にたくさんあります


1
私のScalaプロジェクトで簡単に使用できる非常に素晴らしいパッケージ、ありがとうございます。
zslim

0

jar URLからファイルを一覧表示/読み取るためのちょうど異なる方法で、ネストされた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 
    }
});

0

もう1つは、道路です。

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 );
        }
      }
    }
  }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.