Java를 사용하여 디렉토리의 모든 파일을 재귀 적으로 나열


85

디렉토리에있는 모든 파일의 이름을 재귀 적으로 인쇄하는이 함수가 있습니다. 문제는 반복 할 때마다 원격 네트워크 장치에 액세스해야하기 때문에 내 코드가 매우 느리다는 것입니다.

내 계획은 먼저 디렉토리에서 모든 파일을 재귀 적으로로드 한 다음 정규식이있는 모든 파일을 검토하여 원하지 않는 모든 파일을 필터링하는 것입니다. 누구든지 더 나은 제안이 있습니까?

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

아니요,이 코드가 작동한다는 것을 알고 있지만 매우 느리고 파일 시스템에 대한 어리석은 액세스와 한 번에 모든 것을 가져 오는 대신 모든 하위 디렉토리에 대한 내용을 가져 오는 것 같습니다.
Hultner

답변:


134

이것이 여러분이 작성하게 될 실제 프로덕션 코드라고 가정하면, 이미 해결 된 이런 종류의 솔루션 인 Apache Commons IO , 특히 FileUtils.listFiles(). 중첩 된 디렉토리, 필터 (이름, 수정 시간 등을 기준으로)를 처리합니다.

예를 들어 정규식의 경우 :

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

그러면 ^(.*?)정규식과 일치하는 파일을 재귀 적으로 검색 하여 결과를 컬렉션으로 반환합니다.

이것이 여러분 자신의 코드를 롤링하는 것보다 빠르지 않을 것이라는 점은 주목할 가치가 있습니다. 자바에서 파일 시스템을 트롤링하는 것은 느립니다. 차이점은 Apache Commons 버전에는 버그가 없다는 것입니다.


나는 거기를 보았고 거기에서 commons.apache.org/io/api-release/index.html?org/apache/commons/… 를 사용 하여 디렉토리와 하위 디렉토리에서 모든 파일을 가져온 다음 파일을 검색하여 내 정규식과 일치합니다. 아니면 내가 틀렸나 요?
Hultner

네 문제는 폴더를 스캔하는 데 한 시간 이상이 걸리며 프로그램을 시작할 때마다 업데이트를 확인하는 것은 매우 성가신 일입니다. 프로그램의이 부분을 C로 작성하고 나머지는 Java로 작성하면 더 빠를까요? 그렇다면 중요한 차이가 있습니까? 지금은 if isdir 행의 코드를 변경하고 디렉토리도 검색에 포함 할 정규식과 일치해야하도록 추가했습니다. 귀하의 예에서 DirectoryFileFilter.DIRECTORY라는 것을 알았습니다. 정규식 필터가있을 수 있다고 생각합니다.
Hultner

1
네이티브 호출을 사용하여 작성하면 속도가 절대적으로 빨라집니다. FindFirstFile / FineNextFile을 사용하면 별도의 호출없이 파일 속성을 쿼리 할 수 ​​있습니다. 이는 대기 시간이 더 긴 네트워크에 막대한 영향을 미칠 수 있습니다. 이에 대한 Java의 접근 방식은 매우 비효율적입니다.
Kevin Day

5
@ hanzallah-afgan : 질문과 답변 모두 5 년이 넘었습니다. 과거에 두 개의 주요 Java 릴리스가 있었으므로 Java 7 NIO와 같은 최신 기능을 조사하고 싶지 않을 수 있습니다.
헐 트너 최고

4
github.com/brettryan/io-recurse-tests 라는 성능 히트를 알고 수락하는 경우에만 FileUtils를 사용 하십시오 . 네이티브 java8 대안은 간결하고 효율적인 표기법을 허용합니다. 예 :Files.walk(Paths.get("/etc")).filter(Files::isRegularFile).collect(Collectors.toList())
ccpizza 2017

64

자바 8에서, 비아 1 라이너의 Files.find()임의의 큰 깊이 (예 999)과 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
난 항상 시도 -과 - 자원 블록 스트림을 반환하는 파일 방법을 사용하는 것이 좋습니다 : 그렇지 않으면 자원이 오픈하겠습니다
riccardo.tasso

터미널 작업이 스트림 자체에서 close를 호출하지 않습니까?
Dragas

@Dragas 예. 내 소비자는 단순한 예일뿐입니다. 실생활에서 당신은 더 유용한 것을 할 것입니다.
보헤미안

27

이것은 주어진 루트에서 모든 파일을 가져 오는 매우 간단한 재귀 방법입니다.

Java 7 NIO Path 클래스를 사용합니다.

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에서는 PathsFiles기능 과 함께 디렉토리 트리를 빠르게 탐색하는 방법이 도입되었습니다 . "이전" 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

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
좋지만 하나의 디렉토리에 대한 파일 만 가져옵니다. 모든 하위 디렉토리를 보려면 내 대체 답변을 참조하십시오.
Dan

3
Files.newDirectoryStreamIOException을 던질 수 있습니다. Java7 try-with-statement에 해당 줄을 래핑하여 스트림이 항상 닫히도록 제안합니다 (예외 여부, 필요 없음 finally). 여기를 참조하십시오 : stackoverflow.com/questions/17739362/...
그렉

12

파일 시스템 폴더 내용을 읽기위한 Java의 인터페이스는 성능이 좋지 않습니다. JDK 7은 이러한 종류의 작업에 기본 수준의 성능을 제공해야하는 완전히 새로운 인터페이스로이를 수정합니다.

핵심 문제는 Java가 모든 단일 파일에 대해 기본 시스템 호출을한다는 것입니다. 대기 시간이 짧은 인터페이스에서는 그렇게 큰 문제는 아니지만 대기 시간이 보통 인 네트워크에서는 실제로 합산됩니다. 위의 알고리즘을 프로파일 링하면 성가신 isDirectory () 호출에 대부분의 시간이 소요된다는 것을 알 수 있습니다. isDirectory ()에 대한 모든 단일 호출에 대해 왕복이 발생하기 때문입니다. 대부분의 최신 OS는 파일 / 폴더 목록이 원래 요청되었을 때 이러한 종류의 정보를 제공 할 수 있습니다 (각 개별 파일 경로에서 해당 속성을 쿼리하는 것과 반대).

JDK7을 기다릴 수없는 경우이 지연 시간을 해결하기위한 한 가지 전략은 다중 스레드로 이동하고 최대 스레드 수의 ExecutorService를 사용하여 재귀를 수행하는 것입니다. 좋지는 않지만 (출력 데이터 구조의 잠금을 처리해야 함) 단일 스레드를 수행하는 것보다 훨씬 빠를 것입니다.

이러한 종류에 대한 모든 논의에서 네이티브 코드 (또는 거의 동일한 작업을 수행하는 명령 줄 스크립트)를 사용하여 수행 할 수있는 최선과 비교하는 것이 좋습니다. 네트워크 구조를 통과하는 데 1 시간이 걸린다고해서 그다지 큰 의미는 아닙니다. 7 초 안에 네이티브로 할 수 있지만 자바에서는 1 시간이 걸린다고 말하면 사람들의 관심을 끌 것입니다.


3
이제 Java 7이 있으므로 Java 7에서 수행하는 방법에 대한 예제가 도움이 될 것입니다. 또는 적어도 링크. 또는 Google에서 검색 할 수업 이름입니다. — 이것은«stackoverflow»이며 결국«이론적 인 cs»가 아닙니다 ;-).
Martin

3
글쎄요 ... 제 원래 포스트는 2010 년 3 월 ... 지금은 2012 년 1 월입니다 ... 그리고 방금 제 장비 재고 내역을 확인했는데 10 년 3 월에 타임머신을 가지고 있지 않았어요. 그래서 나는 명백한 예를주지 않고 대답하는 것이 정당하다고 생각한다 ;-)
Kevin Day


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 2011

3

개인적으로이 버전의 FileUtils를 좋아합니다. 다음은 디렉토리 또는 하위 디렉토리에서 모든 mp3 또는 flacs를 찾는 예입니다.

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에 오신 것을 환영합니다. 귀하의 답변이 기존의 많은 답변에 대한 개선 또는 대안임을 명확히 할 수 있습니까?
Lilienthal 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 ()
Matt Jones

1

자바 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

한 번에 모든 것을 가져 오는 대신 파일 시스템에 액세스하고 모든 하위 디렉토리의 내용을 가져 오는 것이 어리석은 것처럼 느껴집니다.

당신의 감정이 잘못되었습니다. 이것이 파일 시스템이 작동하는 방식입니다. 더 빠른 방법은 없습니다 (반복적으로 수행하거나 다른 패턴에 대해 수행해야하는 경우를 제외하고는 모든 파일 경로를 메모리에 캐시 할 수 있지만 캐시 무효화를 처리해야합니다. 앱 실행).


문제는 특정 이름 형식의 특정 유형의 모든 파일을 사용자에게 제공되는 라이브러리로로드하고 앱이 시작될 때마다 라이브러리가 업데이트되어야하지만 라이브러리를 업데이트하는 데 시간이 오래 걸립니다. 내가 얻은 유일한 해결책은 백그라운드에서 업데이트를 실행하는 것이지만 모든 새 파일이로드 될 때까지 너무 오래 걸리는 것은 여전히 ​​성가신 일입니다. 더 나은 방법이 있어야합니다. 또는 적어도 데이터베이스를 업데이트하는 더 좋은 방법입니다. 이미 한 번 통과 한 모든 파일을 통과하는 것은 어리석은 느낌입니다. 업데이트 만 빠르게 찾을 수있는 방법이 있습니까?
Hultner

@Hultner : Java 7에는 파일 시스템 업데이트 알림을받을 수있는 기능이 포함되어 있지만 앱이 실행되는 동안에 만 작동하므로 항상 백그라운드 서비스를 실행하지 않으려면 도움이되지 않습니다. Kevin이 설명하는 것처럼 네트워크 공유에 특별한 문제가있을 수 있지만 전체 디렉토리 트리를 스캔하는 한 더 좋은 방법은 없습니다.
Michael Borgwardt

아마도 일부 색인 파일을 만들 수 있습니다. 디렉토리 크기를 확인하는 방법이 있으면 크기가 변경 될 때 새 파일을 간단히 검색 할 수 있습니다.
James P.

@James : 디렉토리 크기를 확인할 방법이 없습니다. 디렉토리의 크기는 내가 아는 모든 파일 시스템에서 각 파일의 크기를 가져 와서 더하여 얻습니다. 실제로 "이 디렉토리의 크기는 얼마입니까?"라는 질문입니다. 하드 링크를 고려하면 전혀 의미가 없습니다.
Michael Borgwardt

네가 옳아. 나는 여전히 일부 캐싱 및 / 또는 지문이 프로세스 속도를 높일 수 있다고 생각합니다.
James 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

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

Java 8을 사용하여 파일 및 디렉토리를 나열하는 또 다른 예 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.