Javaを使用してディレクトリからすべてのファイルを再帰的に一覧表示します


86

ディレクトリ内のすべてのファイルの名前を再帰的に出力するこの関数があります。問題は、反復ごとにリモートネットワークデバイスにアクセスする必要があるため、コードが非常に遅いことです。

私の計画は、最初にディレクトリからすべてのファイルを再帰的にロードし、その後、正規表現を使用してすべてのファイルを調べて、不要なすべてのファイルを除外することです。誰かより良い提案がありますか?

public static printFnames(String sDir){
  File[] faFiles = new File(sDir).listFiles();
  for(File file: faFiles){
    if(file.getName().matches("^(.*?)")){
      System.out.println(file.getAbsolutePath());
    }
    if(file.isDirectory()){
      printFnames(file.getAbsolutePath());
    }
  }
}

これは後でテストするだけで、このようなコードは使用しません。代わりに、高度な正規表現に一致するすべてのファイルのパスと変更日を配列に追加します。


1
... 質問はなんですか?このコードが機能することの検証を探していますか?
Richard JP Le Guen 2010年

いいえ、このコードが機能することは知っていますが、非常に遅く、すべてを一度に取得するのではなく、ファイルシステムにアクセスしてすべてのサブディレクトリのコンテンツを取得するのは愚かなようです。
ハルトナー2010年

回答:


134

これが実際に作成する本番コードであると仮定すると、この種の解決策を使用することをお勧めします。具体的には、Apache CommonsIOFileUtils.listFiles()です。ネストされたディレクトリ、フィルタ(名前、変更時間などに基づく)を処理します。

たとえば、正規表現の場合:

Collection files = FileUtils.listFiles(
  dir, 
  new RegexFileFilter("^(.*?)"), 
  DirectoryFileFilter.DIRECTORY
);

これにより、^(.*?)正規表現に一致するファイルが再帰的に検索され、結果がコレクションとして返されます。

これは独自のコードをロールするよりも速くはないことに注意してください。同じことを実行します。Javaでファイルシステムをトロールするのは遅いだけです。違いは、ApacheCommonsバージョンにはバグがないことです。


そこを見て、そこからcommons.apache.org/io/api-release/index.html?org/apache/commons/…を使用してディレクトリとサブディレクトリからすべてのファイルを取得し、ファイルを検索して次のようにします。彼らは私の正規表現と一致します。それとも私は間違っていますか?
ハルトナー2010年

ええ、問題はフォルダをスキャンするのに1時間以上かかり、更新をチェックするためにプログラムを起動するたびにそれを行うのは非常に面倒です。プログラムのこの部分をCで記述し、残りをJavaで記述した場合、より高速になりますか?もしそうなら、大きな違いはありますか?今のところ、if isdir行のコードを変更して追加したので、検索に含めるには、ディレクトリも正規表現と一致する必要があります。あなたの例ではDirectoryFileFilter.DIRECTORYと書かれているのがわかりますが、そこに正規表現フィルターを設定できると思います。
ハルトナー2010年

1
ネイティブ呼び出しを使用して書き込むと、絶対に高速になります-FindFirstFile / FineNextFileを使用すると、個別に呼び出すことなくファイル属性を照会できます-これは、待ち時間の長いネットワークに大きな影響を与える可能性があります。これに対するJavaのアプローチは、ひどく非効率的です。
ケビン日

5
@ hanzallah-afgan:質問と回答はどちらも5年以上前のものです。過去に2つの主要なJavaリリースがあったため、Java 7NIOなどの新しい機能を調査したくない場合があります。
ハルトナー2015年

4
FileUtilsは、パフォーマンスの低下を認識して受け入れる場合にのみ使用してください:github.com/brettryan/io-recurse-tests。ネイティブjava8の代替は、簡潔でより効率的な表記を可能にします。例:Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
ccpizza 2017

66

Java 8では、これFiles.find()は任意の深さ(eg 999)の1ライナービアでありBasicFileAttributesisRegularFile()

public static printFnames(String sDir) {
    Files.find(Paths.get(sDir), 999, (p, bfa) -> bfa.isRegularFile()).forEach(System.out::println);
}

さらにフィルタリングを追加するには、ラムダを拡張します。たとえば、過去24時間に変更されたすべてのjpgファイルを追加します。

(p, bfa) -> bfa.isRegularFile()
  && p.getFileName().toString().matches(".*\\.jpg")
  && bfa.lastModifiedTime().toMillis() > System.currentMillis() - 86400000

3
try-with-resourcesブロックでStreamを返すFilesメソッドを常に使用することをお勧めします。それ以外の場合は、リソースを開いたままにします
riccardo.tasso 2017年

ターミナル操作はストリーム自体でcloseを呼び出しませんか?
ドラガス

@Dragasはい。私の消費者は単なる例です。実生活では、もっと便利なことをするでしょう。
ボヘミアン

27

これは、特定のルートからすべてのファイルを取得するための非常に単純な再帰的方法です。

Java 7 NIOPathクラスを使用します。

private List<String> getFileNames(List<String> fileNames, Path dir) {
    try(DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
        for (Path path : stream) {
            if(path.toFile().isDirectory()) {
                getFileNames(fileNames, path);
            } else {
                fileNames.add(path.toAbsolutePath().toString());
                System.out.println(path.getFileName());
            }
        }
    } catch(IOException e) {
        e.printStackTrace();
    }
    return fileNames;
} 

18

Java 7では、PathsandFiles機能を使用してディレクトリツリーをすばやく移動する方法が導入されました。それらは「古い」File方法よりもはるかに高速です。

これは、ウォークスルーして正規表現でパス名を確認するためのコードになります。

public final void test() throws IOException, InterruptedException {
    final Path rootDir = Paths.get("path to your directory where the walk starts");

    // Walk thru mainDir directory
    Files.walkFileTree(rootDir, new FileVisitor<Path>() {
        // First (minor) speed up. Compile regular expression pattern only one time.
        private Pattern pattern = Pattern.compile("^(.*?)");

        @Override
        public FileVisitResult preVisitDirectory(Path path,
                BasicFileAttributes atts) throws IOException {

            boolean matches = pattern.matcher(path.toString()).matches();

            // TODO: Put here your business logic when matches equals true/false

            return (matches)? FileVisitResult.CONTINUE:FileVisitResult.SKIP_SUBTREE;
        }

        @Override
        public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts)
                throws IOException {

            boolean matches = pattern.matcher(path.toString()).matches();

            // TODO: Put here your business logic when matches equals true/false

            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path path,
                IOException exc) throws IOException {
            // TODO Auto-generated method stub
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path path, IOException exc)
                throws IOException {
            exc.printStackTrace();

            // If the root directory has failed it makes no sense to continue
            return path.equals(rootDir)? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE;
        }
    });
}

5
いい答えです:)、「SimpleFileVisitor」と呼ばれる実装されたクラスもあります。実装されたすべての機能が必要ない場合は、必要な関数をオーバーライドするだけです。
galDude33 2014年

13

Java 7 NIOを使用してディレクトリのコンテンツを取得する高速な方法:

import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.FileSystems;
import java.nio.file.Path;

...

Path dir = FileSystems.getDefault().getPath( filePath );
DirectoryStream<Path> stream = Files.newDirectoryStream( dir );
for (Path path : stream) {
   System.out.println( path.getFileName() );
}
stream.close();

3
いいですが、1つのディレクトリのファイルしか取得しません。すべてのサブディレクトリを表示したい場合は、私の代替回答を参照してください。
ダン

3
Files.newDirectoryStreamIOExceptionをスローできます。その行をJava7try-with-statementでラップして、ストリームが常に閉じられるようにすることをお勧めします(例外かどうか、を必要とせずにfinally)。こちらもご覧ください:stackoverflow.com/questions/17739362/…–
Greg

12

ファイルシステムフォルダの内容を読み取るためのJavaのインターフェイスは、あまりパフォーマンスが高くありません(ご存知のとおり)。JDK 7は、この種の操作にネイティブレベルのパフォーマンスをもたらす、この種の完全に新しいインターフェイスでこれを修正します。

中心的な問題は、Javaがすべてのファイルに対してネイティブシステムコールを行うことです。低遅延のインターフェイスでは、これはそれほど大きな問題ではありませんが、中程度の遅延のネットワークでは、実際に合計されます。上記のアルゴリズムのプロファイルを作成すると、時間の大部分が厄介なisDirectory()呼び出しに費やされていることがわかります。これは、isDirectory()への呼び出しごとにラウンドトリップが発生するためです。最近のほとんどのOSは、ファイル/フォルダーのリストが最初に要求されたときにこの種の情報を提供できます(個々のファイルパスにそのプロパティを照会するのではありません)。

JDK7を待つことができない場合、この待ち時間に対処するための1つの戦略は、マルチスレッドに移行し、最大数のスレッドでExecutorServiceを使用して再帰を実行することです。これは素晴らしいことではありませんが(出力データ構造のロックに対処する必要があります)、このシングルスレッドを実行するよりもはるかに高速になります。

この種のことについてのすべての議論では、ネイティブコード(またはほぼ同じことを行うコマンドラインスクリプト)を使用して実行できる最善の方法と比較することを強くお勧めします。ネットワーク構造をトラバースするのに1時間かかると言っても、それほど意味はありません。ネイティブで7秒でできると言っても、Javaでは1時間かかると人々の注目を集めます。


3
Java 7が登場したので、Java7でそれを行う方法の例が役立つでしょう。または少なくともリンク。または、Googleで検索するクラス名。—これは«stackoverflow»であり、結局のところ«理論的なcs»ではありません;-)。
マーティン

3
よく見てみましょう...私の元の投稿は2010年3月でした...現在は2012年1月です...そして機器の在庫履歴を確認したところ、2010年3月にタイムマシンを持っていたことがわかりません。ので、私は;-)明示的な例を与えることなく答える中で、おそらく正当だと思う
ケビン日


7

これは問題なく機能します...そしてその再帰的

File root = new File("ROOT PATH");
for ( File file : root.listFiles())
{
    getFilesRecursive(file);
}


private static void getFilesRecursive(File pFile)
{
    for(File files : pFile.listFiles())
    {
        if(files.isDirectory())
        {
            getFilesRecursive(files);
        }
        else
        {
            // do your thing 
            // you can either save in HashMap and use it as
            // per your requirement
        }
    }
}

1
java <7で動作するものが必要な場合は良い答えです。
ssimm 2016

3

私は個人的にこのバージョンのFileUtilsが好きです。これは、ディレクトリまたはそのサブディレクトリ内のすべてのmp3またはflacを検索する例です。

String[] types = {"mp3", "flac"};
Collection<File> files2 = FileUtils.listFiles(/path/to/your/dir, types , true);

3

これはうまくいきます

public void displayAll(File path){      
    if(path.isFile()){
        System.out.println(path.getName());
    }else{
        System.out.println(path.getName());         
        File files[] = path.listFiles();
        for(File dirOrFile: files){
            displayAll(dirOrFile);
        }
    }
}


StackOverflow Mam'sへようこそ。あなたの答えが、多くの既存の答えの改善または代替である方法を明確にできますか?
リリエンタール2015

1

この関数は、おそらくすべてのファイル名とそのディレクトリおよびそのサブディレクトリからのパスを一覧表示します。

public void listFile(String pathname) {
    File f = new File(pathname);
    File[] listfiles = f.listFiles();
    for (int i = 0; i < listfiles.length; i++) {
        if (listfiles[i].isDirectory()) {
            File[] internalFile = listfiles[i].listFiles();
            for (int j = 0; j < internalFile.length; j++) {
                System.out.println(internalFile[j]);
                if (internalFile[j].isDirectory()) {
                    String name = internalFile[j].getAbsolutePath();
                    listFile(name);
                }

            }
        } else {
            System.out.println(listfiles[i]);
        }

    }

}

1
この例では、listFiles()メソッドがnullを返す可能性があるという事実を考慮していません。docs.oracle.com/javase/7/docs/api/java/io/File.html#listFiles()
マット・ジョーンズ

1

Java 8

public static void main(String[] args) throws IOException {

        Path start = Paths.get("C:\\data\\");
        try (Stream<Path> stream = Files.walk(start, Integer.MAX_VALUE)) {
            List<String> collect = stream
                .map(String::valueOf)
                .sorted()
                .collect(Collectors.toList());

            collect.forEach(System.out::println);
        }


    }

0

すべてを一度に取得するのではなく、ファイルシステムにアクセスしてすべてのサブディレクトリのコンテンツを取得するのはばかげているように感じます。

あなたの気持ちは間違っています。これがファイルシステムの仕組みです。より高速な方法はありません(これを繰り返し実行する必要がある場合や異なるパターンの場合を除いて、すべてのファイルパスをメモリにキャッシュできますが、キャッシュの無効化、つまりファイルが追加/削除/名前変更されたときに何が起こるかを処理する必要がありますアプリが実行されます)。


特定の種類の特定の名前形式のすべてのファイルを、ユーザーに提示されるライブラリにロードしたいのですが、アプリが起動するたびにライブラリが更新されるはずですが、ライブラリの更新には永遠に時間がかかります。私が得た唯一の解決策は、バックグラウンドで更新を実行することですが、すべての新しいファイルがロードされるまでに非常に長い時間がかかるのはまだ面倒です。それを行うためのより良い方法があるはずです。または、少なくともデータベースを更新するためのより良い方法。すでに一度通過したすべてのファイルを通過するのはばかげていると感じます。更新のみをすばやく見つける方法はありますか?
ハルトナー2010年

@Hultner:Java 7には、ファイルシステムの更新の通知を受け取る機能が含まれますが、それでもアプリの実行中にのみ機能するため、バックグラウンドサービスを常に実行する必要がない限り、役に立ちません。Kevinが説明しているように、ネットワーク共有には特別な問題があるかもしれませんが、ディレクトリツリー全体をスキャンすることに依存している限り、これ以上の方法はありません。
Michael Borgwardt 2010年

おそらく、いくつかのインデックスファイルを作成できます。ディレクトリサイズを確認する方法がある場合は、サイズが変更されたときに新しいファイルをスキャンするだけです。
James P.

@James:ディレクトリサイズを確認する方法はありません。ディレクトリのサイズは、私が知っているすべてのファイルシステムで、各ファイルのサイズを取得して合計することで取得されます。実際、「このディレクトリのサイズはどれくらいですか?」という質問です。ハードリンクを検討する場合、必ずしも意味がありません。
Michael Borgwardt

あなたが正しい。キャッシュやフィンガープリントによってプロセスが高速化される可能性があると今でも感じています。
ジェームズP。

0

isDirectory()は非常に遅いメソッドです。ファイルブラウザではかなり遅いと感じています。ライブラリを調べて、ネイティブコードに置き換えます。


0

何百万ものフォルダやファイルを処理する際に私が見つけたより効率的な方法は、DOSコマンドを使用してディレクトリリストをいくつかのファイルにキャプチャし、それを解析することです。データを解析したら、分析を行って統計を計算できます。


0
import java.io.*;

public class MultiFolderReading {

public void checkNoOfFiles (String filename) throws IOException {

    File dir=new File(filename);
    File files[]=dir.listFiles();//files array stores the list of files

 for(int i=0;i<files.length;i++)
    {
        if(files[i].isFile()) //check whether files[i] is file or directory
        {
            System.out.println("File::"+files[i].getName());
            System.out.println();

        }
        else if(files[i].isDirectory())
        {
            System.out.println("Directory::"+files[i].getName());
            System.out.println();
            checkNoOfFiles(files[i].getAbsolutePath());
        }
    }
}

public static void main(String[] args) throws IOException {

    MultiFolderReading mf=new MultiFolderReading();
    String str="E:\\file"; 
    mf.checkNoOfFiles(str);
   }
}

説明も追加してください。
d4Rk 2015

0

Guavaでは、コレクションが返されるのを待つ必要はありませんが、実際にはファイルを反復処理できます。IDoSomethingWithThisFile以下の関数のシグネチャのインターフェースを想像するのは簡単です。

public static void collectFilesInDir(File dir) {
    TreeTraverser<File> traverser = Files.fileTreeTraverser();
    FluentIterable<File> filesInPostOrder = traverser.preOrderTraversal(dir);
    for (File f: filesInPostOrder)
        System.out.printf("File: %s\n", f.getPath());
}

TreeTraverserを使用すると、さまざまなトラバーサルスタイル間を移動することもできます。


0
public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        for (File fObj : dir.listFiles()) {
            if(fObj.isDirectory()) {
                ls.add(String.valueOf(fObj));
                ls.addAll(getFilesRecursively(fObj));               
            } else {
                ls.add(String.valueOf(fObj));       
            }
        }

        return ls;
    }
    public static List <String> getListOfFiles(String fullPathDir) {
        List <String> ls = new ArrayList<String> ();
        File f = new File(fullPathDir);
        if (f.exists()) {
            if(f.isDirectory()) {
                ls.add(String.valueOf(f));
                ls.addAll(getFilesRecursively(f));
            }
        } else {
            ls.add(fullPathDir);
        }
        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getListOfFiles("/Users/srinivasab/Documents");
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}

0

別の最適化されたコード

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class GetFilesRecursive {
    public static List <String> getFilesRecursively(File dir){
        List <String> ls = new ArrayList<String>();
        if (dir.isDirectory())
            for (File fObj : dir.listFiles()) {
                if(fObj.isDirectory()) {
                    ls.add(String.valueOf(fObj));
                    ls.addAll(getFilesRecursively(fObj));               
                } else {
                    ls.add(String.valueOf(fObj));       
                }
            }
        else
            ls.add(String.valueOf(dir));

        return ls;
    }

    public static void main(String[] args) {
        List <String> ls = getFilesRecursively(new File("/Users/srinivasab/Documents"));
        for (String file:ls) {
            System.out.println(file);
        }
        System.out.println(ls.size());
    }
}

より詳細な説明で答えを拡張していただけますか?これは理解に非常に役立ちます。ありがとうございました!
vezunchik

0

Java8を使用してファイルとディレクトリを一覧表示するもう1つの例 filter

public static void main(String[] args) {

System.out.println("Files!!");
        try {
            Files.walk(Paths.get("."))
                    .filter(Files::isRegularFile)
                    .filter(c ->
                            c.getFileName().toString().substring(c.getFileName().toString().length()-4).contains(".jpg")
                            ||
                            c.getFileName().toString().substring(c.getFileName().toString().length()-5).contains(".jpeg")
                    )
                    .forEach(System.out::println);

        } catch (IOException e) {
        System.out.println("No jpeg or jpg files");
        }

        System.out.println("\nDirectories!!\n");
        try {
            Files.walk(Paths.get("."))
                    .filter(Files::isDirectory)
                    .forEach(System.out::println);

        } catch (IOException e) {
            System.out.println("No Jpeg files");
        }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.