NIO (New Input/Output) (참고 서적 : 이것이 자바다)
java.nio 패키지는 자바4부터 포함된 패키지로, 새로운 입출력이라는 뜻을 가지고 있다.
자바7에서는 네트워크 지원과 IO와 NIO사이의 클래스 일관성을 강화해 NIO.2 API가 추가되었다.
NIO는 IO 와 다르게 스트림이 아닌 채널 방식을 사용하며, 버퍼와 비동기 방식을 기본적으로 제공한다.
또한, IO가 블로킹 방식만 지원하는 것과 달리 NIO는 블로킹과 넌 블로킹 방식을 모두 지원한다.
스트림 vs 채널
스트림 기반에서 입출력을 위해 입력 스트림과 출력스트림을 모두 생성해야하는 것과 달리,
채널은 스트림과 달리 양방향으로 입출력이 가능하다.
non 버퍼 vs 버퍼
IO는 입출력을 위한 버퍼를 제공해주는 보조 스트림이 있다. NIO는 기본적으로 버퍼를 사용해 입출력을 하기 때문에 성능이 좋다.
IO가 스트림에 읽은 데이터를 즉시 처리하는 것과 달리 NIO에서는 읽은 데이터를 무조건 버퍼에 저장한다.
이는 별도로 입력된 데이터를 저장하지 않아도, 버퍼내에서 데이터의 위치를 이동해 가며 필요한 부분을 읽고 쓸 수 있는 유연성을 제공한다.
블로킹(blocking : 대기상태) vs non 블로킹
IO에서 입력 스트림의 read() 메소드를 호출하면 데이터가 입력되기 전까지 스레드가 블로킹(대기상태)가 된다. write() 메소드도 마찬가지로 출력되기전까지 블로킹 상태가 된다. 블로킹 상태에서 다른 일을 할 수 없고 이 상태를 빠져나오기 위해 인터럽트 될 수도 없다. 블로킹을 빠져나오기 위해선 스트림을 닫는 수 밖에 없다.
non 블로킹은 스레드를 인터럽트함으로써 빠져나올 수 있으며, 스레드가 블로킹 되지 않는다. 이는 입출력 준비가 완료된 채널만 선택해, 작업 스레드가 처리하기 때문에 블로킹 되지 않는다.
어떤 경우에 IO와 NIO중에서 취사 선택해야 하는가?
IO는 연결 클라이언트 수가 적고, 전송되는 데이터가 대용량으로 순차처리 될 때에 사용하는 것이 적다.
NIO는 연결 클라이언트 수가 많고, 하나의 입출력 처리 작업이 오래 걸리지 않는 경우에 사용하는 것이 좋다. 처리가 오래 걸리면 대기 작업의 수가 늘어나기 때문에 비효율적이다. 또한, 모든 입출력에 버퍼를 사용하기 때문에 대용량 데이터의 처리에는 버퍼 할당의 문제도 있다.
경로 정의 및 탐색
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import java.nio.file.Path; import java.nio.file.Paths; import java.util.Iterator; public class PathExample { public static void main(String[] args) throws Exception { Path path = Paths.get("src/sec02/exam01_path/PathExample.java"); // or ("src/sec02/", "exam01_path/PathExample.java"); System.out.println("[파일명] " + path.getFileName()); System.out.println(("부모 디렉토리명]: " + path.getParent().getFileName())); System.out.println("중첩 경로수: " + path.getNameCount()); System.out.println(); for(int i=0; i<path.getNameCount(); i++) { System.out.println(path.getName(i)); } System.out.println(); Iterator<Path> iterator = path.iterator(); while(iterator.hasNext()) { // 이동할 항목이 있다면 true 리턴 Path temp = iterator.next(); // 현재 위치를 순차적으로 하나 증가해 이동 System.out.println(temp.getFileName()); } } } | cs |
WatchService
자바 7에서 도입된 와치 서비스는, 디렉토리 내부에서 파일 생성, 삭제, 수정 등의 변화를 감시하는데 사용된다.
(에디터에서 파일이 변경되면 다시 불러올 것인지를 묻는 것이 와치 서비스의 예)
와치 서비스 실행 결과 및 예제 (Java FX)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchEvent.Kind; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.List; import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.control.TextArea; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; public class WatchServiceExample extends Application { class WatchServiceThread extends Thread { @Override public void run() { try { WatchService watcher = FileSystems.getDefault().newWatchService(); Path directory = Paths.get("C:/temp"); directory.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); while(true) { WatchKey watchKey = watcher.take(); // 블로킹(Watch키가 큐에 들어올 때까지) List <WatchEvent<?>> list = watchKey.pollEvents(); // WatchEvent목록 얻음 for(WatchEvent<?> watchEvent : list) { // 이벤트 종류 얻음 Kind<?> kind = watchEvent.kind(); // 감지된 path 얻음 Path path = (Path)watchEvent.context(); if(kind == StandardWatchEventKinds.ENTRY_CREATE) { Platform.runLater(()-> textArea.appendText("파일 생성됨 " + path.getFileName() + "\n")); } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { Platform.runLater(()-> textArea.appendText("파일 삭제됨 " + path.getFileName() + "\n")); } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { Platform.runLater(()-> textArea.appendText("파일 변경됨 " + path.getFileName() + "\n")); } else if (kind == StandardWatchEventKinds.OVERFLOW) { } } boolean valid = watchKey.reset(); if(!valid) { break; } } } catch (Exception e) { } } } TextArea textArea = new TextArea(); @Override public void start(Stage primaryStage) throws Exception { BorderPane root = new BorderPane(); root.setPrefSize(500, 300); textArea.setEditable(false); root.setCenter(textArea); Scene scene = new Scene(root); primaryStage.setScene(scene); primaryStage.setTitle("워치 서비스 예제"); primaryStage.show(); WatchServiceThread wst = new WatchServiceThread(); wst.start(); } public static void main(String[] args) throws Exception { launch(args); } } | cs |
non 다이렉트 버퍼와 다이렉트 버퍼
non 다이렉트 버퍼는 JVM이 관리하는 heap 메모리 공간을 이용하는 버퍼로 버퍼 생성 시간이 빠르다. 반면에 heap 공간은 상당히 제한되어 있으므로 버퍼의 크기를 크게 잡을 수가 없다. 또한 입출력을 위해 non다이렉트 버퍼의 내용을 임시 다이렉트 버퍼를 생성해 복사하고, 그곳에서 OS의 native I/O와 같은 기능을 수행하기 때문에 입출력 속도는 느리다.
반면에 다이렉트 버퍼는 OS의 native C 함수를 호출하고 여러 가지 처리할 것이 있기 때문에 버퍼 생성이 느리다. 하지만 OS가 관리하는 메모리 공간을 이용하므로 버퍼의 크기가 크고, 입출력 성능이 높다는 장점이 있다. 따라서 한 번 생성해놓고, 빈번한 입출력과 큰 데이터 처리에 사용하는 것이 효율이 좋다.
다음 예제에서는 non 다이렉트와 다이렉트 버퍼를 생성하고 그 결과를 보는 예제이다. non 다이렉트 버퍼는 앞서 설명했듯 JVM이 관리하는 제한된 heap메모리 공간을 사용하므로 크기가 작아, OutOfMemoryError가 발생한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | import java.nio.ByteBuffer; public class BufferSizeExample { public static void main(String[] args) { ByteBuffer directBuffer = ByteBuffer.allocateDirect(200 * 1024 * 1024); System.out.println("다이렉트 버퍼 생성 " + directBuffer); ByteBuffer nonDirectBuffer = ByteBuffer.allocateDirect(2000 * 1024 * 1024); System.out.println("non 다이렉트 버퍼 생성 " + nonDirectBuffer); } } | cs |
다이렉트 버퍼 생성 java.nio.DirectByteBuffer[pos=0 lim=209715200 cap=209715200]
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:693)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at BufferSizeExample.main(BufferSizeExample.java:9)
allocate() 메소드
JVM heap 메모리에 non다이렉트 버퍼 생성.
1 2 | ByteBuffer byteBuffer = ByteBuffer.allocate(100); // 최대 100개의 바이트를 저장하는 ByteBuffer 생성 CharBuffer charBuffer = CharBuffer.allocate(100); // 최대 100개의 문자를 저장하는 CharBuffer 생성 | cs |
Wrap() 메소드
각 타입별 Buffer클래스는 모두 wrap() 메소드를 가지고 있다. wrap() 메소드는 이미 생성된 자바 배열은 Wrapping해서 Buffer 객체를 생성한다.
자바 배열은 JVM heap 메모리에 생성되므로(배열도 객체이다) wrap()은 non다이렉트 버퍼를 생성한다.
1 2 | byte[] byteArray = new byte[100]; ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray); | cs |
다음과 같이 배열의 일부 데이터만 Buffer객체를 생성할 수도 있다.
1 2 3 4 | char[] charArray = new char[100]; // 0 인덱스부터 50개만 버퍼로 생성 CharBuffer charBuffer = CharBuffer.wrap(charArray, 0, 50); | cs |
CharBuffer는 CharSequence 인터페이스로 구현한 클래스이므로 CharSequence 타입의 매개값을 갖는 wrap() 메소드도 제공한다. 즉, 매개값으로 문자열을 제공해서 다음과 같이 CharBuffer 생성이 가능하다.
1 | CharBuffer charBuffer = CharBuffer.wrap("문자열 매개값 넣기"); | cs |
CharSequence 인터페이스
http://impartially.tistory.com/23
allocateDirect() 메소드
이 메소드는 다이렉트 버퍼(OS가 메모리 관리)를 생성하며, ByteBuffer만 제공한다. 각 타입별 버퍼는 없지만 as-Buffer()를 통해 각 타입별 Buffer를 얻을 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.IntBuffer; public class DirectBufferCapacityExample { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100); System.out.println("저장용량: " + byteBuffer.capacity() + " 바이트"); CharBuffer charBuffer = ByteBuffer.allocateDirect(100).asCharBuffer(); System.out.println("저장용량: " + charBuffer.capacity() + " 문자"); IntBuffer intBuffer = ByteBuffer.allocateDirect(100).asIntBuffer(); System.out.println("저장용량: " + intBuffer.capacity() + " 정수"); } } | cs |
byte 해석 순서
1 2 3 4 5 6 7 8 | import java.nio.ByteOrder; public class ComputerByteOrderExample { public static void main(String[] args) { System.out.println("운영체제 종류 : " + System.getProperty("os.name")); System.out.println("네이티브 바이트 해석 순서 : " + ByteOrder.nativeOrder()); } } | cs |
결과 :
운영체제 종류 : Windows 7
네이티브 바이트 해석 순서 : LITTLE_ENDIAN
CPU 등 환경에 따라 바이트의 처리 순서에는 차이가 있다. (Little Endian, Big Endian)
따라서, 네트워크로 데이터를 주고 받을 때는 하나의 Endian에 맞도록 Byte Order해줄 필요가 있다.
JVM은 무조건 Big Endian으로 자동적으로 처리해주지만, 다이렉트 버퍼일 경우 OS의 native I/O를 사용하므로
다음과 같이 기본 해석순서로 맞춰주면 성능에 도움이 된다.
1 | ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100).order(ByteOrder.nativeOrder()); | cs |
'Study > Java' 카테고리의 다른 글
Java Network 프로그래밍 기초 (0) | 2017.06.03 |
---|---|
NIO 기반 네트워킹 (0) | 2017.05.30 |
Java - OOP_인터페이스와 다형성 (0) | 2017.03.20 |
Java - OOP_클래스와 상속, 생성자, 오버로딩/오버라이딩 (0) | 2017.03.17 |
Java 개발PC 설정 및 기초 (0) | 2017.03.16 |