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
'자바' 카테고리의 다른 글
java21 - scoped value에 대해서 알아보자 (0) | 2023.11.26 |
---|---|
java 21 처리량 향상을 위한 대안 - virtual thread 알아보자 (0) | 2023.11.24 |
java collection sort using Comparator (0) | 2023.10.17 |
java generic class (0) | 2023.09.24 |
java time convert (시간 변환) (0) | 2023.09.17 |
댓글