티스토리 뷰

Skill/Programming - Network

19. Java NIO (New IO)

gyulee0220 2019. 9. 11. 20:55



1. Java IO

  이전에도 한번 설명한 바가 있는데, 자바에서 지원하는 기본 스트림 기반의 입출력 체계를 I/O 혹은 I/O 스트림 이라고 한다. I/O는 당연히 각각 Input과 Output을 뜻한다. Java IO라는 단어만 보게 된다면, Java 프로그램에서 사용되는 Input과 Output 이라는 의미다. 그리고 Java에선 기본적으로 Stream 방식의 IO를 사용하므로 Java IO라는 단어를 보게 되면 당연하게 우린 스트림을 떠올리게 된다.


  Java IO는 아래와 같은 특징을 가진다.

  • Stream 방식
  • 버퍼 사용하지 않음
  • 블로킹 방식 (Blocking)


  Java IO 는 바이트(Byte) 방식의 입력과 문자열(Char) 방식의 입력 2가지로 크게 나뉜다.


<Byte Stream>

InputStream / OutputStream을 통해서 입출력


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
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
 
public class Solution {
 
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
 
        // InpuStream
        InputStream is = System.in;
        
        byte[] a = new byte[3];
                
        is.read(a);
                
        // OutputStream
        OutputStream os = System.out;
        
        os.write(a);
        
    }
 
}
 
cs


<Char Stream>

Reader / Writer을 통해서 입출력


- Reader


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
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
 
public class Solution {
 
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
 
        // Reader
        Reader reader = new InputStreamReader(System.in);
            
        char b = 0;
        
        while (true){
            int a = reader.read();
            if(a == -1break;
            b = (char)a;
            System.out.print(b);
 
        }        
 
    }
 
}
 
cs



- Writer


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
 
public class Solution {
 
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
 
        // Reader
        Writer writer = new OutputStreamWriter(System.out);
            
        writer.write("Hello world");
        writer.close();
        
        
    }
 
}
cs



  스트림 방식의 전통적인 Java IO는 위와 같이 프로그래밍 할 수 있다. 물론 이 4가지 클래스 이외에도 다른 방식의 IO도 지원한다. 

  • 파일 방식의 IO : FIleInputStream, FileOutputStream, FileReader, FileWriter
  • 데이터 방식의 IO : DataInputStream, DataOutputStream

2. Java NIO

  네트워크 프로그램의 속도는 우리 컴퓨터의 CPU나 메모리에서 주고받는 속도에 비해 현저히 느리다, 속도는 컴퓨터 환경에서 느린데 오고가는 데이터의 양은 더 많은 양이 오고간다. CPU와 메모리 디스크야 하드웨어의 발전에 따라 속도가 점점 빨라진다. 듀얼코어, 쿼드코어가 생기고, SSD가 탄생하면서 더욱 빨라진다. 하지만 유무선 네트워크 환경 역시 발전하고 있지만 분명 한계는 존재한다. 그렇기에 기존의 IO 방식과는 다른 개념이 필요하다.


  더군다나 Stream 방식의 입출력은 병목현상에 매우 취약하다. 보내는 데이터가 Queue에 계속 쌓이다보면 앞의 데이터 처리가 되어야 뒤의 데이터가 처리 되는데, 처리 속도보다 데이터의 진입 속도가 더 빠르다면 병목현상이 지속되게 된다. 이를 해결하기 위해 네트워크 환경에서는 다른 방식의 입출력이 필요하다.


  Java에서는 New IO라는 이름의 채널 기반의 새로운 입출력 시스템을 2002년 부터 지원하게 되었다. 흔히 우리가 Non-blocking IO로 알고있는 NIO는 Non-blocking하게 데이터를 주고 받는다.


- Blocking 방식


  Blocking 방식하에서는 모든 스레드가 병렬 구조로 설계되어있다. 즉 스레드간의 계층이 전혀 없다. 따라서 JVM에서 스레드를 진행할 때, 입출력이 필요한 경우 스레드를 블로킹 하여 처리한다. 입력 스트림에서 Reader()를 호출하면 스레드가 블로킹 되고 Writer()를 호출해야 블로킹이 풀리게 된다.


- Non-Blocking 방식


  Non-Blocking 방식에선 입출력에 따라 스레드가 블로킹 상태가 되지 않는다. 이가 가능한 것이 하나의 스레드가 모든 스레드의 입출력을 관장하는 멀티플렉서 역할을 하게 된다. 입출력을 관장하는 스레드가 준비 완료된 채널을 선택해서 IO를 진행한다. 즉, 채널 방식의 IO를 지원한다.


  채널로 동작하는 NIO에서는 체널을 통한 양방향 통신이 가능하다. 따라서, 입력과 출력을 할때 매번 스트림 객체를 생성해야하는 기존 방법과 달리 Channel 선언 하나로 입출력 모두 처리 할 수 있다는 것이 기존 방식과 가장 큰 차이이다.


  아래는 Java NIO에서 지원하는 클래스이다.


   NIO 클래스

  설명 

 java.nio

 NIO 버퍼 클래스 

 java.nio.channels

 NIO 버커 채널 클래스

 java.nio.charset

 문자셋 클래스

 java.nio.file

 파일 접근 클래스



3, 버퍼 (Buffer)

  java NIO에서는 입력받거나 출력해야할 데이터를 항상 Buffer에 저장하게 된다. 즉, NIO를 사용하게 된다면 우리가 입력한 데이터는 버퍼로 이동하게 되는 것이고, 우리가 출력받아 보는 데이터는 버퍼에 있는 데이터이다. 그리고 이를 채널이 관장하는 방식으로 진행된다.



  버퍼는 받아들이는 데이터 타입에 따라 분류된다.

  • ByteBuffer : 바이트 타입의 데이터를 받는 버퍼

  • CharBuffer : 문자열 타입의 데이터를 받는 버퍼

  • ShortBuffer : Short형 타입의 데이터를 받는 버퍼

  • IntBuffer : 정수형 타입의 데이터를 받는 버퍼

  • LongBuffer : Long형 타입의 데이터를 받는 버퍼

  • FloatBuffer : Float형 타입의 데이터를 받는 버퍼

  • DoubleBuffer : Double형 타입의 데이터를 받는 버퍼


  버퍼는 크게 다이렉트형 버퍼와 논다이렉트형 버퍼 2개로 나눌 수 있다.


   구분

   Direct Buffer

   Non-Direct Buffer

 위치

  운영체제의 메모리

  JVM 힙 메모리

 생성 시간

  느리다

  빠르다

 크기

  크다

  작다

 성능

  높다

  낮다


  간단한 텍스트 파일을 불러오는 버퍼를 생성하는 경우 아래와 같이 만들 수 있다. 이때 다이렉트 버퍼와 논다이렉트 버퍼간의 속도 차이를 한번 살펴보자.


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
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
 
public class Solution {
 
    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub
 
        // 파일 경로 지정
        Path path = Paths.get("src/TEXT.txt");
        
        long size = Files.size(path);
        
        FileChannel fileChannel = FileChannel.open(path);
        
        // Non-Direct Buffer
        ByteBuffer nondirectbuffer = ByteBuffer.allocate((int) size); 
        
        // Direct Buffer
        ByteBuffer directbuffer = ByteBuffer.allocateDirect((int) size); 
        
        long start, end;
        
        start = System.nanoTime();
        
        for (int i = 0; i < 100; i++) {
            fileChannel.read(nondirectbuffer);
            nondirectbuffer.flip();
 
        }
 
        end = System.nanoTime();
 
        System.out.println("Non-Direct Buffer : " + (end - start) + " ns");
 
        start = System.nanoTime();
 
        for (int i = 0; i < 100; i++) {
            fileChannel.read(directbuffer);
            directbuffer.flip();
 
        }
 
        end = System.nanoTime();
        System.out.println("Direct Buffer : " + (end - start) + " ns");
 
        fileChannel.close();
 
    }
 
}
 
cs


  두 버퍼의 속도 차이는 아래와 같다.



  앞서 살펴본 바와 같이 버퍼를 할당하기 위해서는 allocate()와 allocateDirect() 메소드를 통해 할당한다. 버퍼에 대해서는 바로 다음 장의 글에서 조금더 심도 있게 살펴보도록 하자.


4. IO와 NIO의 선택

  IO와 NIO 방식의 기본 개념과 간단한 데이터 읽는 코드에 대해 알아보았다. 이론적인 부분만 보았을땐 채널이 존재하는 NIO 방식을 적용하는 것이 월등히 좋아보인다. 하지만 실제 네트워크 환경에서는 반드시 NIO 방식이 효율적이지는 않다.


  NIO 방식의 장점은 스레드 생성이 계속 발생하는 것을 막을 수 있다는 점이다. 하지만 아무리 이런 방식을 사용해도 데어터 처리 과정 자체가 어렵다면 당연히 NIO 방식에도 병목현상은 일어난다. 또한 NIO의 가장 큰 단점은 사전 작업이다. 스레드에서 직접 스트림으로 데이터를 주고받는 IO 방식은 사전작업에 소요되는 비용히 매우 적다. 반면에 NIO는 앞에서 살펴본대로 버퍼를 어딘가엔 만들어 주어야 한다.


  대용량 처리를 하게된다면 이 단점을 더욱 부각되는 데, NIO 방식에서는 대용량의 크기 만큼의 버퍼를 생성해줘야 한다. 이 과정에서 소요되는 시간이 더 길어지면 NIO방식 보다 IO 방식이 훨씬 유리하다.


  만약 당신이 IO 방식을 선택해야 한다면 다음과 같은 기준을 따르자.


<Java IO>

  • 대용량의 데이터를 한번에 주고 받는 경우
  • 연결 클라이언트 수가 적은 경우
  • 하나의 입출력 처리 작업이 오래 걸리는 경우
  • 데이터를 순차적으로 처리해야하는 경우


<Java NIO>

  • 주고받는 데이터의 용량이 작은 경우
  • 연결된 클라이언트의 수가 큰 경우
  • 하나의 입출력 처리 작업이 오래 걸리지 않는 경우
  • 데이터 조회, 변경을 한곳에 저장하고 자주 불러내는 경우




'Skill > Programming - Network' 카테고리의 다른 글

21. 채널 (Channel)  (0) 2019.09.27
20. 버퍼 (Buffer)  (0) 2019.09.20
18. 서버 소켓 프로그래밍 예제 (Java)  (0) 2019.09.08
17. 네트워크 보안 및 보안 소켓  (0) 2019.08.31
16. 서버 소켓  (0) 2019.08.16
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/04   »
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
글 보관함