자바

java - 디렉토리 삭제 + 특정 조건의 파일 삭제하기

알쓸개잡 2024. 3. 31.

코드를 통해서 디렉터리를 삭제하고자 하는 경우에는 해당 디렉터리가 clean 한 상태에서 삭제를 해야 한다.

하위 디렉토리를 포함하여 디렉터리에 파일이 없더라도 하위 디렉터리 역시 없어야 삭제가 가능하다.

이번 포스팅에서는 다음과 같은 방법으로 디렉토리를 삭제하는 방법을 적어보고자 한다.

  • File 클래스
  • Files 클래스
  • apache commons io 라이브러리의 FileUtils

디렉터리를 삭제하기 위해서는 기본적으로 파일 목록을 가져와서 처리해야 하는데 디렉터리의 파일 목록을 추출하는 코드에 대해서는 아래 포스팅을 참고하기 바란다.

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

 

샘플 디렉토리 구조

삭제 대상 디렉토리 구성은 다음과 같다.

삭제 대상 디렉토리 구조

 

 

File 클래스를 이용한 디렉터리 삭제

java.io 패키지에 포함된 File 클래스는 파일에 대한 여러 가지 기능을 제공한다.

File 클래스에서 제공하는 listFiles 메서드를 사용하여 재귀적으로 하위 디렉터리 및 파일을 삭제할 수 있다.

@Test
@DisplayName( "File 클래스를 사용하여 하위 디렉토리를 포함한 디렉토리 삭제하기" )
void delete_directory_by_file() {
    //class path의 resources/dir-for-file 경로 획득
    Path path = Path.of(
            Objects.requireNonNull( this.getClass().getResource( "directory" ) ).getPath());

    File directory = path.toFile();
    deleteDirectoryRecursivelyByFile( directory );
}

void deleteDirectoryRecursivelyByFile(File directory) {
    System.out.println("now directory: " + directory);
    File[] files = directory.listFiles();
    if ( files != null ) {
        Arrays.stream( files )
                .sorted( Comparator.comparing( file -> file.isDirectory() ? 1 : -1 ) )
                .forEach( file -> {
                    if ( file.isDirectory() ) {
                        deleteDirectoryRecursivelyByFile( file );
                    }
                    else {
                        if ( file.delete() ) {
                            System.out.println( "deleted: " + file );
                        }
                    }
                } );
        if ( directory.delete() ) {
            System.out.println( "deleted: " + directory);
        }
    }
}

파일을 먼저 삭제하고 하위 디렉토리를 처리하도록 정렬하였다.

listFiles() 메서드를 통해서 해당 디렉터리의 파일 목록(디렉터리 포함)을 가져온다. 하지만 디렉터리의 하위 파일 목록 까지는 추출하지 않으므로 하위 디렉터리에 대한 파일 목록을 가져오려면 재귀적으로 처리를 해야 한다.

File 클래스의 listFiles() 메서드는 FileFilter, FilenameFilter 함수형 인터페이스를 파라미터로 전달 받아 특정 조건의 파일 목록만 추출할 수도 있다.

 

다음 코드는 FileFilter를 사용하여 파일명에서 '1-1' 로 끝나는 파일만 삭제하는 코드다.

@Test
@DisplayName( "File 클래스를 이용한 특정 조건의 파일만 삭제" )
void delete_directory_by_files_conditional() {
    //class path의 resources/dir-for-file 경로 획득
    Path path = Path.of(
            Objects.requireNonNull( this.getClass().getResource( "directory" ) ).getPath());

    deleteFileConditionalRecursively( path.toFile() );
}

private void deleteFileConditionalRecursively(File directory) {
    File[] conditionalFiles = directory.listFiles( 
            file -> file.isFile() && file.getName().endsWith( "1-1" ) 
    );

    //delete conditional file
    if ( conditionalFiles != null ) {
        Arrays.stream( conditionalFiles )
                .forEach( file -> {
                    if ( file.delete() ) {
                        System.out.println( "deleted: " + file );
                    }
                } );
    }

    //process sub directories
    File[] subDirectories = directory.listFiles( File::isDirectory );
    if ( subDirectories != null ) {
        for ( File subDirectory : subDirectories ) {
            deleteFileConditionalRecursively( subDirectory );
        }
    }
}

directory.listFiles() 메서드에 FileFilter 인터페이스에 대한 구현체를 lambda로 전달하였다.

타입이 파일이고 파일명이 '1-1'로 끝나는 파일 목록만 가져와서 삭제한다.

이후 하위 디렉토리만 가져와서 재귀적으로 처리를 하도록 한다.

listFiles()에 FileFilter 인스턴스를 전달하지 않고 모든 파일 목록을 가져온 뒤에 stream().filter()를 통해서 처리해도 상관없을 듯하다.

 

 

Files 클래스를 이용한 디렉터리 삭제

java.nio.file 패키지에 포함된 Files 클래스는 지정된 디렉터리의 파일 목록을 가져오는 list() 메서드를 제공한다.

Files 클래스의 walk() 메서드는 자신의 디렉터리 목록도 포함되므로 삭제시에는 사용하지 않는 것이 좋을 것 같다.

대신 list() 메서드를 사용하는 것이 좋겠다.

Dependency

/* maven */
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.16.0</version>
</dependency>

=========================================

/* gradle */
implementation 'commons-io:commons-io:2.16.0'

 

@Test
@DisplayName( "Files 클래스를 사용하여 하위 디렉토리를 포함한 디렉토리 삭제하기" )
void delete_directory_by_files() {
    //class path의 resources/dir-for-file 경로 획득
    Path path = Path.of(
            Objects.requireNonNull( this.getClass().getResource( "directory" ) ).getPath());
    try {
        deleteDirectoryRecursivelyByFiles( path );
    } catch ( IOException e ) {
        System.out.println("fail to delete, " + path + ", " + e.getMessage());
    }
}

private void deleteDirectoryRecursivelyByFiles(Path directory) throws IOException{
    System.out.println("now directory: " + directory);
    Files.list( directory )
            .sorted( Comparator.comparing( path -> Files.isDirectory(path) ? 1 : -1 ) )
            .forEach( path -> {
                if ( Files.isDirectory( path ) ) {
                    try {
                        deleteDirectoryRecursivelyByFiles( path );
                    } catch ( IOException e ) {
                        System.out.println("fail to delete, " + path + ", " + e.getMessage());
                    }
                }
                else {
                    try {
                        Files.delete( path );
                        System.out.println( "deleted: " + path );
                    }
                    catch ( IOException e ) {
                        System.out.println("fail to delete, " + path + ", " + e.getMessage());
                    }
                }
            });

    Files.delete( directory );
    System.out.println( "deleted: " + directory);
}

 

 

Apache Commons IO 라이브러리를 이용한 디렉토리 삭제

apache commons io 라이브러리는 파일 및 디렉토리 처리를 위한 다양한 유틸리티를 제공한다.

다음 샘플 코드는 디렉토리 삭제와 디렉터리의 특정 조건의 파일만 삭제하는 코드다.

@Test
@DisplayName( "apache commons-io 를 이용한 디렉토리 삭제" )
void delete_directory_by_commons_io_test() {
    Path path = Path.of(
            Objects.requireNonNull( this.getClass().getResource( "directory" ) ).getPath());

    try {
        FileUtils.deleteDirectory( path.toFile() );
        System.out.println("deleted: " + path);
    }
    catch ( IOException e ) {
        System.out.println("fail to directory, " + path + ", " + e.getMessage());
    }
}

@Test
@DisplayName( "apache commons-io 를 이용한 특정 조건의 파일만 삭제" )
void delete_conditional_file_by_commons_io_test() throws IOException {
    Path path = Path.of(
            Objects.requireNonNull( this.getClass().getResource( "directory" ) ).getPath());

    FileUtils.streamFiles( path.toFile(), true, ( String[] )null )
            .filter( file -> file.getName().endsWith( "1-1" ) )
            .forEach( file -> {
                if ( file.delete() ) {
                    System.out.println( "deleted: " + file);
                }
            } );
}

기본 자바에서 제공하는 File, Files 클래스를 이용하는 코드에 비해서 상당히 간단하게 처리할 수 있다.

내부적으로 재귀적으로 처리해 준다.

특정 조건의 파일(파일명이 '1-1'로 끝나는 파일)의 경우에는 streamFiles() 메서드의 두 번째 파라미터를 true로 지정하여 하위 디렉토리의 파일 목록까지 가져오도록 한다.

 

결론

각각의 방식에 대한 장단점은 다음과 같다.

  • File 및 Files
    • 내장 및 표준: 외부 종속성 없이 Java SE API에서 사용할 수 있다. (장점)
    • 직접적: 기본 파일 작업 및 표준 Java 기능만 필요할 때 사용 가능하다. (장점)
    • 복잡성: 복잡할 수 있는 재귀적 구성을 직접 구현해야 한다. (단점)
  • Apache Commons IO
    • 사용편의성: 재귀적 구성을 직접 구성할 필요 없이 메서드 한 번의 호출로 처리가 가능하다. (장점)
    • 다양한 파일 조작 유틸리티를 제공한다. (장점)
    • 외부 종속성이 발생한다. (단점)
    • 다양한 기능들이 포함되어 있어서 애플리케이션의 크기가 커질 수 있다. (단점)

정리하자면

외부 종속성을 최소화하는 것이 우선순위인 경우 Java에 내장된 File, Files 클래스를 사용.

광범위한 파일 조작이 필요한 프로젝트나 더 적은 코드로 복잡한 I/O 작업을 처리하고자 하는 경우에는 commons io를 사용.

하는 것이 좋겠다.

 

끝.

 

댓글

💲 추천 글