elevne's Study Note

Java - NIO (1) 본문

Backend/Java

Java - NIO (1)

elevne 2023. 5. 28. 21:47

저번 시간에는 입출력을 위한 Java 의 IO API 에 대해서 알아보았다. Java IO 에서 이미 입출력을 위한 모든 필요한 클래스들을 제공해주기는 하지만, JDK 4 부터는 더 빠른 입출력을 위한 Java NIO 를 사용할 수 있게 되었다. Java NIO 는 IO 의 대체제와 같은 것으로, 고성능의 네트워킹과 파일 조작을 할 수 있다. NIO 는 IO 와는 다른 방식으로 작동된다. Java 의 입출력 작업에 필요한 모든 클래스를 포함하는 java.io.package 와 마찬가지로 java.nio 패키지는 NIO API 전체에서 사용되는 버퍼 클래스를 정의한다. Java NIO 는 다음 두 이유로 사용된다고 한다.

 

 

  1. Non-Blocking 방식: NIO 는 Non-Blocking 방식의 IO 작업을 수행한다. 이는 이미 준비된 데이터를 읽는다는 뜻이다. 예를 들어 하나의 스레드는 채널에 버퍼의 데이터를 읽도록 요청한 후, 해당 스레드는 그 기간 동안 다른 작업을 위해 이동한 후 이전 시점에서 다시 시작할 수 있다. 그렇다면 다른 작업을 하는 사이에 읽기 작업이 완료되어 전체 효율이 증가하게 되는 것이다. 
  2. Buffer-Oriented 접근방식: NIO 의 버퍼 지향적인  접근법을 사용하면 필요에 따라 버퍼를 앞뒤로 이동할 수 있다고 한다. NIO 에서 데이터는 버퍼로 읽혀지고 그곳에 캐시된다. 데이터가 필요할 때마다 버퍼에서 추가로 처리되는 것이다.

 

이러한 강점을 지닌 NIO 는 아래 3 개의 Core Component 들에 기반한다. 

 

 

  1. Buffer: 원시 데이터 타입들에 대해서 Buffer 을 사용할 수 있다. Java NIO 는 버퍼 지향적인 패키지로, 버퍼에 데이터를 쓰고, 읽을 수 있으며 버퍼에서 채널을 사용하여 추가 처리해줄 수 있다. 여기서 버퍼는 원시 타입의 데이터를 저장하고 다른 NIO 패키지에 정보를 넘기기에 컨테이너와 같은 역할을 하는 것이라고 볼 수 있다. 
  2. Channel: 채널은 새로운 기본 IO abstraction 이다. 채널은 스트림과 유사하다고 볼 수 있는데, 채널은 스트림과 다르게 데이터를 읽을 수도 쓸 수도 있다. 서로 다른 엔티티에 대한 연결은 Non-Blocking 방식의 IO 동작을 수행할 수 있는 다양한 채널로 표시된다. 채널은 매체 또는 게이트웨이의 역할을 한다.
  3. Selector: Non-Blocking 방식에 Selector 을 사용할 수 있다. 이는 이벤트에 대해 여러 채널을 모니터링하는 객체이다. Java NIO 가 Non-Blocking IO 작업을 수행할 때 채널을 선택할 수 있는 셀렉터와 선택 키가 다중화된 IO 작업을 정의한다. 

 

 

Channel, Buffer

 

 

 

Selector

 

 

 

NIO 에서는 이전에 사용하던 클래스들이 아니라 좀 더 다양한 파일의 속성 정보를 제공해주는 java.nio.file, java.nio.file.attribute 패키지의 클래스와 인터페이스를 사용한다. 우선 java.io.File 의 역할을 하는 java.nio.file.Path 인터페이스에 대해 알아본다. NIO 에서 파일의 경로를 지정하기 위해서는 Path 인터페이스를 사용한다. Path 의 구현 객체를 얻기 위해서는 Paths 클래스의 get() 메소드를 호출해야 한다. 

 

 

 

public static void main(String[] args) throws Exception {
    Path path = Paths.get("/Users/wonil/Documents/MxYkYudhLLvj.jpg");
    System.out.println(path.getFileName());
    System.out.println(path.getParent().getFileName());
}

 

 

result

 

 

 

위처럼 절대경로를 이용할 수도, 혹은 상대경로를 이용하여 Path 객체를 만들 수도 있다. Path 객체로부터 파일명, 부모 디렉토리 명, 중첩 경로 수(getNameCount()), 경로 상에 있는 디렉토리 정보(getName(n)) 등을 얻을 수 있다. 

 

 

 

java.nio.file.Files 클래스를 사용하여 파일과 디렉토리의 생성 및 삭제, 그리고 속성을 읽어볼 수 있다. 

 

 

public static void main(String[] args) throws Exception {
    Path path = Paths.get("/Users/wonil/Documents/MxYkYudhLLvj.jpg");
    System.out.println("IS IT DIRECTORY?: "+ Files.isDirectory(path));
    System.out.println("IS IT A FILE?: "+Files.isRegularFile(path));
    System.out.println("WHEN IS THE LATEST MODIFIED TIME?: "+Files.getLastModifiedTime(path));
    System.out.println("FILE SIZE?: "+Files.size(path));
    System.out.println("FILE OWNER?: "+Files.getOwner(path));
    System.out.println("IS IT HIDDEN FILE?: "+Files.isHidden(path));
    System.out.println("IS IT READABLE?: "+Files.isReadable(path));
    System.out.println("IS IT WRITABLE?: "+Files.isWritable(path));
}

 

 

result

 

 

 

위처럼 파일의 각종 정보를 알아볼 수도,

 

 

public static void main(String[] args) throws Exception {
    Path path1 = Paths.get("/Users/wonil/Documents/Temp");
    Path path2 = Paths.get("/Users/wonil/Documents/Temp/nio_test.txt");
    if (Files.notExists(path1)){
        Files.createDirectories(path1);
        Files.createFile(path2);
    }
}

 

 

result

 

 

파일 및 디렉토리를 생성해볼 수도 있다.

 

 

 

그 다음으로는 WatchService 라는 것에 대해 알아볼 필요가 있다. 이는 자바 7에서 처음으로 소개된 것으로, 디렉토리 내부에서 파일 생성, 삭제, 수정 등의 내용 변화를 감시하는데 사용된다. (흔히 사용되는 예는, 에디터에서 파일을 편집하고 있을 때 에디터 바깥에서 파일 내용을 수정하게 되면 파일 내용이 변경되었으니 파일을 다시 불러올 것인지 묻는 창을 띄우는 것) 

 

 

WatchService 객체를 만들고, Path 객체에서 register() 메소드를 호출하여 해당 디렉토리에 대한 WatchSerivce 를 등록해줄 수 있다. 디렉토리에 WatchService 를 등록한 순간부터 디렉토리 내부에서 변경이 발생되면 와치 이벤트가 발생하고, WatchService 는 해당 이벤트 정보를 가진 WatchKey 를 생성하여 큐에 넣어준다. 프로그램은 무한 루프를 돌면서 WatchService 의 take() 메소드를 호출하여 WatchKey 가 큐에 들어올 때까지 대기하다가 WatchKey 가 큐에 들어오면 이를 얻어 처리하는 것이다.

 

 

public static void main(String[] args) throws Exception {
    Thread thread = new Thread(){
        @Override
        public void run() {
            try {
                WatchService watchService = FileSystems.getDefault().newWatchService();
                Path directory = Paths.get("/Users/wonil/Documents/Temp");
                directory.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
                        StandardWatchEventKinds.ENTRY_DELETE,
                        StandardWatchEventKinds.ENTRY_MODIFY);
                while (true) {
                    WatchKey watchKey = watchService.take();
                    List<WatchEvent<?>> list = watchKey.pollEvents();
                    for (WatchEvent watchEvent : list) {
                        WatchEvent.Kind kind = watchEvent.kind();
                        if (kind.equals(StandardWatchEventKinds.ENTRY_CREATE)) {
                            System.out.println("FILE CREATED");
                        } else if (kind.equals(StandardWatchEventKinds.ENTRY_DELETE)) {
                            System.out.println("FILE DELETED");
                        } else if (kind.equals(StandardWatchEventKinds.ENTRY_MODIFY)) {
                            System.out.println("FILE MODIFIED");
                        }
                    }
                    boolean valid = watchKey.reset();
                    if (!valid) break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    thread.start();
}

 

 

result

 

 

 

 

 

Reference:

이것이 자바다

https://www.geeksforgeeks.org/introduction-to-java-nio-with-examples/

 

'Backend > Java' 카테고리의 다른 글

Java - NIO (3)  (0) 2023.05.30
Java - NIO (2)  (0) 2023.05.29
Java - Reflection  (1) 2023.05.27
Java - Socket(UDP)  (0) 2023.05.26
Java - Socket(TCP)  (0) 2023.05.25