자바

[Java] 특정 디렉토리의 파일 목록 가져오기

알쓸개잡 2023. 11. 4.

Java API를 사용하여 숨겨진 파일을 포함하여 디렉터리에 있는 모든 파일을 재귀적으로 가져오는 방법에 대해서 알아보자.

  • Files.list()
  • Files.walk()
  • DirectorySystem
  • File.listFiles()
  • File.listFiles(filter)
  • Stream.filter()

 

테스트를 위한 샘플 디렉토리 구조는 다음과 같다.

샘플 디렉토리 구조

sample-directory의 절대경로는 /java-project/blog/filelist/sample-directory이다.

 

Directory의 파일만 리스팅

Files.list()를 이용한 파일 목록 스트리밍

하위 디렉터리와 하위 디렉토리 파일을 제외한 해당 디렉터리의 파일 목록만 가져올 때 사용한다.

//samplePath의 Path 타입이고 경로는 /java-project/blog/filelist/sample-directory/ 이다.
@Test
void files_list_test() throws IOException {
    try (Stream<Path> pathStream = Files.list(samplePath)) {
        List<File> list =
            pathStream
                .map(Path::toFile)
                .filter(File::isFile)
                .toList();
        list.forEach(System.out::println);
    }
}

 

/java-project/blog/filelist/sample-directory/file2.txt
/java-project/blog/filelist/sample-directory/file1.txt
/java-project/blog/filelist/sample-directory/file4.extension
/java-project/blog/filelist/sample-directory/file3.extension

 

 

DirectoryStream을 이용한 파일 목록

DirectoryStream은 looping을 사용하여 디렉터리의 항목을 반복하는 데 사용된다.

DirectoryStream을 닫으면 스트림과 관련된 모든 리소스가 해제된다.

스트림을 닫지 않으면 resource leak이 발생할 수 있으므로 try-with-resources 구문을 사용하는 것이 좋다.

@Test
void directory_stream_test() {
    List<File> fileList = new ArrayList<>();
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(samplePath)){
        stream.forEach(path -> {
            if (!Files.isDirectory(path)) {
                fileList.add(path.toFile());
            }
        });
    } catch (IOException e) {
        System.out.println("IOException " + e.getMessage());
    }

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

 

/java-project/blog/filelist/sample-directory/file2.txt
/java-project/blog/filelist/sample-directory/file1.txt
/java-project/blog/filelist/sample-directory/file4.extension
/java-project/blog/filelist/sample-directory/file3.extension

 

Directory의 하위 디렉터리를 포함한 파일 리스팅

Files.walk()를 이용한 파일목록 스트리밍

Files.walk() 메서드는 지정된 시작 디렉터리에서 시작하여 디렉토리 depth를 순회한 결과에 대한 stream을 반환한다.

@Test
void files_walk_test() {
    List<Path> pathList = new ArrayList<>();
    try (Stream<Path> pathStream = Files.walk(samplePath)) {
        pathList = pathStream.map(Path::normalize)
            .filter(Files::isRegularFile)
            .toList();
    } catch (IOException e) {
        System.out.println("IOException " + e.getMessage());
    }

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

 

/java-project/blog/filelist/sample-directory/file2.txt
/java-project/blog/filelist/sample-directory/file1.txt
/java-project/blog/filelist/sample-directory/directory2/directory2-2/directory2-2-file1
/java-project/blog/filelist/sample-directory/directory2/directory2-1/directory2-1-file1
/java-project/blog/filelist/sample-directory/file4.extension
/java-project/blog/filelist/sample-directory/file3.extension
/java-project/blog/filelist/sample-directory/directory1/directory1-2/directory1-2-file1
/java-project/blog/filelist/sample-directory/directory1/directory1-1/directory1-1-file1

하위 디렉터리의 파일을 포함한 목록을 출력하였다.

하위 디렉터리를 포함하려면 filter(Files::isRegularFile)을 제거하면 된다.

파일 목록이 아닌 디렉토리 목록만 출력하기 위해서는 filter(Files::isDirectory)로 수정하면 된다.

@Test
void files_walk_test() {
    List<Path> pathList = new ArrayList<>();
    try (Stream<Path> pathStream = Files.walk(samplePath)) {
        pathList = pathStream.map(Path::normalize)
            .filter(Files::isDirectory)
            .toList();
    } catch (IOException e) {
        System.out.println("IOException " + e.getMessage());
    }

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

 

/java-project/blog/filelist/sample-directory
/java-project/blog/filelist/sample-directory/directory2
/java-project/blog/filelist/sample-directory/directory2/directory2-2
/java-project/blog/filelist/sample-directory/directory2/directory2-1
/java-project/blog/filelist/sample-directory/directory1
/java-project/blog/filelist/sample-directory/directory1/directory1-2
/java-project/blog/filelist/sample-directory/directory1/directory1-1

 

Recursion을 사용한 파일 목록

File.isDirectory()와 File.listFiles()를 이용하여 재귀적으로 파일 목록을 가져올 수 있다.

@Test
void recursion_test() {
    List<File> files = listFiles(samplePath.toString());
    files.forEach(System.out::println);
}

private List<File> listFiles(String path) {
    List<File> fileList = new ArrayList<>();
    File[] files = new File(path).listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            fileList.addAll(listFiles(file.getPath()));
        } else {
            fileList.add(file);
        }
    }

    return fileList;
}
/java-project/blog/filelist/sample-directory/file2.txt
/java-project/blog/filelist/sample-directory/file1.txt
/java-project/blog/filelist/sample-directory/directory2/directory2-2/directory2-2-file1
/java-project/blog/filelist/sample-directory/directory2/directory2-1/directory2-1-file1
/java-project/blog/filelist/sample-directory/file4.extension
/java-project/blog/filelist/sample-directory/file3.extension
/java-project/blog/filelist/sample-directory/directory1/directory1-2/directory1-2-file1
/java-project/blog/filelist/sample-directory/directory1/directory1-1/directory1-1-file1

 

File 객체의 listFiles() 메서드의 경우 FileFilter 혹은 FilenameFilter를 인자로 전달하여 특정 조건의 파일 목록을 가져올 수 있다.

FileFilter는 Predicate 유형의 함수형 인터페이스로써 다음과 같다.

@FunctionalInterface
public interface FileFilter {

    /**
     * Tests whether or not the specified abstract pathname should be
     * included in a pathname list.
     *
     * @param  pathname  The abstract pathname to be tested
     * @return  {@code true} if and only if {@code pathname}
     *          should be included
     */
    boolean accept(File pathname);
}

 

FilenameFilter 도 지원하는데 역시 BiPredicate 유형의 함수형 인터페이스로써 다음과 같다.

@FunctionalInterface
public interface FilenameFilter {
    /**
     * Tests if a specified file should be included in a file list.
     *
     * @param   dir    the directory in which the file was found.
     * @param   name   the name of the file.
     * @return  {@code true} if and only if the name should be
     * included in the file list; {@code false} otherwise.
     */
    boolean accept(File dir, String name);
}

 

FileFilter와 FilenameFilter는 람다 표현식으로 정의할 수 있는데 람다 표현식에 대해서는 다음 링크를 참고하기 바란다.

2023.09.15 - [자바] - Java - 람다 표현식(lambda expression) - 4개의 주요 functional interface

2023.09.10 - [자바] - Java - 람다 표현식 (lambda expression) 개요

 

FileFilter와 FilenameFilter를 사용하여 특정 파일만 가져오거나 특정 확장자를 가진 파일만 가져오는 테스트 코드다.

@Test
void recursion_test() {
    //파일 목록에서 확장자가 .txt 인 파일 목록만 추출
    FilenameFilter filenameFilter =
        (file, name) ->	name.endsWith(".txt");
    
    //FileFilter를 대체하여 사용 가능하다.
    //물론 listFiles의 2번째 파라미터 타입은 FileFilter로 변경해야 한다.
    //FileFilter filter = file -> file.getName().endsWith(".txt");

    List<File> files = listFiles(samplePath.toString(), filenameFilter);
    files.forEach(System.out::println);
}

private List<File> listFiles(String path, FilenameFilter filter) {
    List<File> fileList = new ArrayList<>();
    File[] files = new File(path).listFiles(filter);
    for (File file : files) {
        if (file.isDirectory()) {
            fileList.addAll(listFiles(file.getPath(), filter));
        } else {
            fileList.add(file);
        }
    }

    return fileList;
}

 

/java-project/blog/filelist/sample-directory/file2.txt
/java-project/blog/filelist/sample-directory/file1.txt

 

*** 특정 확장자의 파일 목록을 가져오는 것은 Stream 방식을 사용하는 경우에도 filter를 통해서 다음과 같이 사용 가능하다.

@Test
void files_walk_extension_test() {
    List<Path> pathList = new ArrayList<>();
    try (Stream<Path> pathStream = Files.walk(samplePath)) {
        pathList = pathStream.map(Path::normalize)
            .filter(Files::isRegularFile)
            .filter(file -> file.getFileName().toString().endsWith(".extension"))
            .toList();
    } catch (IOException e) {
        System.out.println("IOException " + e.getMessage());
    }

    pathList.forEach(System.out::println);
}
/java-project/blog/filelist/sample-directory/file4.extension
/java-project/blog/filelist/sample-directory/file3.extension

 

댓글

💲 추천 글