Preface
이번 장을 끝으로 드디어 '이것이 자바다' 책 1회독을 마쳤다.
중간중간 완벽히 이해하지 못하고 넘어갔던 부분들은 필요할 때 다시 한 번 책과 인터넷을 찾아보며 공부할 생각이다.
물론 자바의 기본적인 사용 방법과 관련된 내용이었던 1권과 달리 2권은 다소 복잡한 내용들을 담고 있으므로 시간이 날 때마다 틈틈이 복습해야 할 것 같다.
1. NIO 소개
- NIO(New Input Output): java.nio 패키지에 포함되어 있는 비동기 채널 등의 네트워크 지원을 강화한 입출력 기능
- IO와 NIO의 차이점
구분 | IO | NIO |
입출력 방식 | 스트림 방식 | 채널 방식 |
버퍼 방식 | non-buffer | buffer |
비동기 방식 | 지원 안 함 | 지원 |
블로킹 / 넌블로킹 방식 | 블로킹 방식만 지원 | 두 방식 모두 지원 |
- IO는 스트림 기반, NIO는 채널(Channel) 기반이다.
→ 채널은 스트림과 달리 양방향으로 입출력이 가능해 하나의 채널로 입출력 기능을 구현할 수 있다.
- IO는 버퍼를 제공해주는 보조 스트림을 사용하지만, NIO는 기본적으로 버퍼를 사용해 입출력을 한다.
1) IO는 스트림에서 읽은 데이터를 즉시 처리하므로, 스트림으로부터 입력된 전체 데이터를 별도로 저장하지 않으면 입력된 데이터를 자유롭게 이용할 수 없다.
2) NIO는 읽은 데이터를 무조건 버퍼에 저장한다.
→ NIO의 입출력 성능이 더 좋다.
- IO는 블로킹 된다.
1) IO 스레드가 블로킹되면 다른 일을 할 수 없고 블로킹을 빠져나오기 위한 인터럽트도 할 수 없다.
2) 블로킹을 빠져나오는 유일한 방법은 스트림을 닫는 것이다.
- NIO는 블로킹과 넌 블로킹 특징을 모두 가지고 있다.
1) NIO 블로킹은 스레드를 인터럽트함으로써 빠져나올 수 있다.
2) 넌블로킹이란 입출력 작업시 스레드가 블로킹되지 않는 것을 의미한다.
3) NIO의 넌블로킹은 입출력 작업 준비가 완료된 채널만 선택해서 작업 스레드가 처리하므로 작업 스레드가 블로킹되지 않는다.
4) NIO 넌블로킹의 핵심 객체는 멀티플렉서(multiplexor)인 셀렉터(Selector)이다.
→ 셀렉터: 복수 개의 채널 중 준비 완료된 채널을 선택하는 방법을 제공
- NIO의 사용
1) 과도한 스레드 생성을 피하고 스레드를 효과적으로 재사용한다.
2) 운영체제의 버퍼(다이렉트 버퍼)를 이용한 입출력이 가능하므로 입출력 성능이 향상된댜.
→ 연결 클라이언트 수가 많고, 하나의 입출력 작업이 오래 걸리지 않는 경우에 좋다.
∵ 연결 클라이언트 수가 적고, 전송되는 데이터가 대용량이며 순차적으로 처리될 필요성이 있을 경우엔 IO를 사용하는 것이 좋다.
2. 파일과 디렉토리
- Path는 IO의 java.io.File 클래스에 대응되는 NIO 인터페이스이다.
1) NIO의 API에서 파일의 경로를 지정하기 위해 Path를 사용한다.
2) Path 구현 객체를 얻기 위해선 java.nio.file.Paths 클래스의 정적 메소드인 get( ) 메소드를 호출하면 된다.
Path path = Paths.get(String first, String... more)
Path path = Paths.get(URI uri);
- 운영체제의 파일 시스템은 FileSystem 인터페이스를 통해 접근할 수 있다.
→ FileSystem 구현 객체는 getDefault( )로 얻을 수 있다.
FileSystem fileSystem = FileSystems.getDefault();
- 와치 서비스(WatchService): 디렉토리 내부에서 파일 생성, 삭제, 수정 등의 내용 변화를 감시하는데 사용
→ 일반적으로 파일 변경 통지 메커니즘으로 알려져 있다.
- WatchService를 생성하려면 FileSystem의 newWatchService( ) 메소드를 호출하면 된다.
WatchService watchService = FileSystems.getDefault().newWatchService();
- WatchService를 생성한 후엔 감시가 필요한 디렉토리의 Path 객체에서 register( ) 메소드로 WatchService를 등록하면 된다.
→ 어떤 변화(생성, 삭제, 수정)를 감시할 것인지를 StandardWatchEventKinds 상수로 지정할 수 있다.
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
- 디렉토리에 서비스를 등록한 순간부터 디렉토리 내부에서 변경이 발생되면 와치 이벤트가 발생하고, WatchService는 해당 이벤트 정보를 가진 WatchKey를 생성하여 큐에 넣는다.
→ 프로그램은 무한 루프를 돌며 WatchService의 take( ) 메소드를 호출하여 WatchKey가 큐에 들어오면 WatchKey를 얻어 처리하면 된다.
- WatchKey를 얻은 후엔 pollEvents( ) 메소드를 호출해서 WatchEvent 리스트를 얻어야 한다.
List<WatchEvent<?>> list = watchKey.pollEvents();
- WatchKey가 더 이상 유효하지 않게 되면 무한 루프를 빠져나와 WatchService의 close( ) 메소드를 호출하면 된다.
3. 버퍼
- 버퍼: 읽고 쓰기가 가능한 메모리 배열
1) 저장되는 데이터 타입에 따라 분류될 수 있다.
2) 어떤 메모리를 사용하느냐에 따라 분류될 수 있다.
→ 다이렉트, 넌다이렉트
- 메모리에 따른 버퍼
1) 넌다이렉트 버퍼: JVM이 관리하는 힙 메모리 공간을 이용하는 버퍼
→ 각 Buffer 클래스의 allocate( )와 wrap( ) 메소드를 호출하여 생성한다.
2) 다이렉트 버퍼: 운영체제가 관리하는 메모리 공간을 이용하는 버퍼
→ ByteBuffer의 allocateDirect( ) 메소드를 호출하여 생성한다.
- 바이트 해석 순서
1) Big endian: 앞쪽 바이트부터 처리하는 것
2) Little endian: 뒤쪽 바이트부터 처리하는 것
- Buffer의 위치 속성
속성 | 설명 |
position | 현재 읽거나 쓰는 위치값이다. 인덱스 값이므로 0부터 시작하며, limit보다 큰 값을 가질 수 없다. 만약 position과 limit의 값이 같아진다면 더 이상 데이터를 쓰거나 읽을 수 없다는 뜻이 된다. |
limit | 버퍼에서 읽거나 쓸 수 있는 위치의 한계를 나타낸다. 이 값은 capacity보다 작거나 같은 값을 가진다. 최초에 버퍼를 만들었을 땐 capacity와 같은 값을 가진다. |
capacity | 버퍼의 최대 데이터 개수를 나타낸다. 인덱스 값이 아니라 수량이다. |
mark | reset( ) 메소드를 실행했을 때에 돌아오는 위치를 지정하는 인덱스로서 mark( ) 메소드로 지정할 수 있다. 반드시 position 이하의 값으로 지정해야 한다. position이나 limit의 값이 mark 값보다 작은 경우, mark는 자동 제거된다. mark가 없는 상태에서 reset( ) 메소드를 호출하면 InvalidMarkException이 발생한다. |
→ 0 <= mark <= position <= limit <= capaticy
4. 파일 채널
- FileChannel은 동기화 처리가 되어 있으므로 멀티 스레드 환경에 안전하다.
- FileChannel은 정적 메소드인 open( )을 호출해서 얻거나, IO의 FileInputStream, FileOutputStream의 getChannel( ) 메소드를 호출해서 얻을 수도 있다.
FileChannel fileChannel = FileChannel.open(Path path, OpenOption... options);
- 파일 채널을 더 이상 이용하지 않을 땐 close( ) 메소드를 호출해 닫아주어야 한다.
5. 파일 비동기 채널
- FileChannel의 read( )와 write( ) 메소드는 파일 입출력 작업 동안 블로킹된다.
→ NIO는 불특정 다수의 파일 및 대용량 파일의 입출력 작업을 위해 비동기 파일 채널을 별도로 제공한다.
- 비동기 파일 채널(AsynchronousFileChannel): 파일의 데이터 입출력을 위해 read( )와 write( ) 메소드를 호출하면 스레드풀에게 작업 처리를 요청하고 이 메소드들을 즉시 리턴시킨다.
1) 실질적인 입출력 작업 처리는 스레드풀의 작업 스레드가 담당한다.
2) 작업 스레드가 입출력을 완료하면 콜백 메소드가 자동 호출되므로, 작업 완료 후 실행해야 할 코드가 있다면 콜백 메소드에 작성하면 된다.
- AsynchronousFileChannel은 두 가지 정적 메소드인 open( )을 호출해서 얻을 수 있다.
//첫 번째 open() 메소드: 파일의 Path 객체와 열기 옵션 값을 매개값으로 받는다.
//내부적으로 생성되는 기본 스레드풀을 이용해서 스레드를 관리한다.
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
Path file,
OpenOption... options
);
//두 번째 open() 메소드: 기본 스레드풀의 최대 스레드 개수는 개발자가 지정할 수 없으므로 사용하는 방법
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
Path file,
Set<? extends OpenOption> options,
ExecutorService executor,
FileAttribute<?>...attrs
);
- AsynchronousFileChannel을 더 이상 사용하지 않을 땐 close( ) 메소드를 호출해서 닫아준다.
6. NIO를 이용한 TCP/UDP 구현
- NIO를 이용해서 TCP 서버/클라이언트 애플리케이션을 개발하려면 블로킹, 넌블로킹, 비동기 구현 방식 중 하나를 결정해야 한다.
- NIO에서 UDP 채널은 DatagramChannel이다.
→ TCP 채널과 마찬가지로 블로킹과 넌블로킹 방식으로 사용할 수 있다.
'Java > 이것이 자바다' 카테고리의 다른 글
Java 기본 개념 (0) | 2023.09.13 |
---|---|
네트워킹 (0) | 2023.05.12 |
IO 기반 입출력 (0) | 2023.05.12 |
이자바 16장(스트림과 병렬 처리) 확인문제 (0) | 2023.05.07 |
스트림과 병렬 처리 (0) | 2023.05.07 |
댓글