본문 바로가기
Old Posts/Java

Java NIO - 파일 다루기 (Files 클래스)

by A6K 2021. 1. 3.

Java NIO에는 파일을 손쉽게 다룰 수 있는 유틸성 메소드를 모아둔 Files 라는 클래스가 있다. 주로 Path 인터페이스와 함께 사용해서 파일과 디렉토리를 다루는데 사용된다.

Files.exists()

주어진 Path에 해당하는 파일이 파일시스템에 존재하는지 여부를 확인한다.

Path path = Paths.get("/tmp/test.txt");

boolean exists = Files.exists(path, LinkOption.NOFOLLOW_LINKS);

Path 객체를 가져와서 Files.exists() 메소드의 첫 번째 인자로 넘겨준다. 두 번째 인자로 준 LinkOption.NOFOLLOW_LINKS는 exists() 메소드가 파일 경로를 찾아갈 때 심볼릭 링크는 따라가지 않겠다는 의미다.

Files.createDirectory()

Path에 해당하는 디렉토리를 만든다.

Path path = Paths.get("/tmp/newDir");

try {
    Path newDir = Files.createDirectory(path);
} catch(FileAlreadyExistsException e){
    // path에 해당하는 디렉토리 혹은 파일이 이미 존재함
} catch (IOException e) {
    // 그 밖에 뭔가 문제가 있음
}

디렉토리의 생성이 성공하면 만들어진 디렉토리에 대한 Path 객체가 반환된다.

만약 만들려고하는 경로에 파일이나 디렉토리가 이미 존재하면 java.nio.file.FileAlreadyExistsException이 발생한다

Files.copy()

파일을 복사한다.

Path source      = Paths.get("/tmp/sourceFile");
Path destination = Paths.get("/tmp/destinationFile");

try {
    Files.copy(source, destination);
} catch(FileAlreadyExistsException e) {
    // 파일이 이미 존재하는 경우
} catch (IOException e) {
    // 뭔가 다른 문제가 발생한 경우
}

터미널에서 cp 명령을 생각하면 된다. source에 해당하는 파일을 destination으로 복사한다.

만약 복사하려고하는 경로에 파일이나 디렉토리가 이미 존재하면 java.nio.file.FileAlreadyExistsException이 발생한다. 디렉토리가 존재하지 않는 등 다른 문제가 발생하면 IOException이 발생할 수도 있다.

만약 destination에 해당하는 파일이 존재해도 덮어쓰고 싶은 경우 StandardCopyOption.REPLACE_EXISTING 옵션을 주면된다.

Path source      = Paths.get("/tmp/sourceFile");
Path destination = Paths.get("/tmp/destinationFile");

try {
    Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
    // 뭔가 다른 문제가 발생한 경우
}

 

Files.move()

파일의 위치를 이동한다. 터미널에서 mv 명령의 실행을 생각하면 된다.

Path source      = Paths.get("/tmp/sourceFile");
Path destination = Paths.get("/tmp/destinationFile");

try {
    Files.move(source, destination, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
    // 실패..
}

copy() 명령과 대체로 비슷하다. 

Files.delete()

파일을 삭제한다.

Path path      = Paths.get("/tmp/sourceFile");

try {
    Files.delete(path);
} catch (IOException e) {
    // 실패..
}

Files.walkFileTree()

파일 시스템 트리를 재귀적으로 순회하는 기능이다. 다른 메소드처럼 Path 객체를 인자로 받으며, FileVisitor 객체도 인자로 받는다.

Path 객체는 순회를 시작할 디렉토리의 경로를 의미한다. FileVisitor 인터페이스는 파일 시스템의 디렉토리들을 순회하는 동안 호출된다. FileVisitor 인터페이스는 다음과 같다.

public interface FileVisitor {

    // 디렉토리를 방문하기 전에 호출되는 메소드
    public FileVisitResult preVisitDirectory(
        Path dir, BasicFileAttributes attrs) throws IOException;
   
    // 파일에 다다랐을 때 호출되는 메소드
    public FileVisitResult visitFile(
        Path file, BasicFileAttributes attrs) throws IOException;

    // 파일에 다다르지 못 했을 때 호출되는 메소드 (권한문제 등)
    public FileVisitResult visitFileFailed(
        Path file, IOException exc) throws IOException;

    // 디렉토리를 방문하고 나서 호출되는 메소드
    public FileVisitResult postVisitDirectory(
        Path dir, IOException exc) throws IOException {
}

디렉토리를 순회하면서 실행할 작업들을 이 인터페이스에 구현해서 넘겨주면된다. walkFileTree() 메소드는 인자로 받은 디렉토리를 시작으로 서브디렉토리를 순회하면서 FileVisitor의 메소드를 호출해준다.

주의해야할 점은 visitFile() 메소드는 디렉토리에 방문할 때는 호출되지 않는 다는 점이다. 오직 파일에 대해서만 호출된다. visitFileFailed() 메소드는 파일 방문이 실패했을 때 호출된다. 예를 들어 권한이 없는 경우가 해당된다.

4개의 메소드는 FileVisitResult 값을 리턴한다. 이 값들에 따라 추가로 순회를 할지 여부가 결정된다.

  • CONTINUE
    • 순회 작업을 계속한다
  • TERMINATE
    • 순회를 바로 종료한다
  • SKIP_SIBLINGS
    • 같은 부모를 갖는 디렉토리 엔트리(Sibling)들에 대한 순회를 생략한다
  • SKIP_SUBTREE
    • 서브트리에 대한 순회를 생략한다.
    • 이 메소드는 preVisitDirectory() 메소드에서 리턴될 경우에 효과가 나타난다
    • 그 밖에는 CONTINUE와 같다.

예를 들어 특정 이름을 갖는 파일 하나를 찾을 때, visitFile()에서 이름을 비교해서 TERMINATE를 호출해 쓸데 없는 순회를 하지 않도록 할 수도 있다.

Files.walkFileTree() 예제 1 - 트리탐색 순서

FileVisitor 인터페이스를 구현한 다음 클래스를 이용해보자.

public class VerySimpleFileVisitor implements FileVisitor<Path> {
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
        System.out.println("preVisit : " + dir.toString());

        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        System.out.println("visitFile : " + file.toString());

        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) {
        System.out.println("visitFileFaied : " + file.toString());

        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
        System.out.println("postVisit : " + dir.toString());

        return FileVisitResult.CONTINUE;
    }
}

서브트리를 순회하면서 호출되는 순서를 보기 위해서 구현한 매우 간단한 FileVisitor다. 

Path path = Paths.get("/tmp/dir1");

try {
    Files.walkFileTree(path, new VerySimpleFileVisitor());
} catch(IOException e){
    e.printStackTrace();
}

이 코드를 실행하면,

이런 결과를 얻게 된다.

FileVisitor 인터페이스의 모든 메소드를 구현할 필요는 없다. SimpleFileVisitor를 상속받아서 필요한 메소드만 오버라이드해서 구현해도 된다.

Files.walkFileTree() 예제 1 - java로 구현한 'rm -rf'

walkFileTree() 메소드와 Files.delete() 메소드를 이용해서 디렉토리를 지우는 동작을 구현해볼 수도 있다. 터미널에서 rm -rf 를 수행한 것과 같다.

Path path = Paths.get("/tmp/dir1");

try {
  Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
      System.out.println("delete file: " + file.toString());
      Files.delete(file);
      return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
      Files.delete(dir);
      System.out.println("delete dir: " + dir.toString());
      return FileVisitResult.CONTINUE;
    }
  });
} catch(IOException e){
  e.printStackTrace();
}

 

이 밖에도 다양한 파일 관련 메소드들이 제공된다. 자세한 내용은 필요할 때마다 찾아보는 걸로하자.

댓글