on
13주차 과제: I/O.
해당 글을 백기선 님의 자바 스터디 13주차 과제를 공부하고 공유하기 위해서 작성되었습니다.
목표
자바의 input과 output에 대해 학습한다.
학습할 것
- 스트림(Stream) / 버퍼(Buffer) / 채널(Channel) 기반의 I/O
- InputStream과 OutputStream
- Byte와 Character 스트림
- 표준 스트림 (System.in, System.out, System.err)
- 파일 읽고 쓰기
입출력 I/O
I/O란 Input과 Output의 약자로 입력과 출력, 간단히 입출력이라고 한다. 입출력은 컴퓨터 내부 또는 외부의 장치와 프로그램 간의 데이터를 주고받는 것을 말한다.
스트림(Stream)
자바에서 입출력을 수행하려면, 즉 어느 한 쪽에서 데이터를 전달하려면, 두 대상을 연결하고 데이터를 전송할 수 있는 무언가가 필요한데 이것을 스트림(Stream)이라고 정의했다. 스트림은 TV와 DVD를 연결하는 입력선과 출력선 같은 역할이다.
스트림이란 데이터를 운반하는데 사용되는 연결통로이다.
스트림이라는 이름은 연속적인 데이터의 흐름을 물에 비유해서 붙여진 것인데, 여러가지로 유사한 점이 많다. 물이 한쪽 방향만 흐르는 것과 같이 스트림은 단방향 통신만 가능하기 때문에 하나의 스트림으로 입력과 출력을 동시에 처리할 수 없다.
입력과 출력을 동시에 수행하려면 입력을 위한 입력스트림(input stream)과 출력을 위한 출력스트림(output stream), 모두 2개의 스트림이 필요하다.
스트림은 큐(queue)와 같은 선입선출의 구조이다. 따라서 먼저 보낸 데이터를 먼저 받게 되어있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고받는다.
InputStream과 OutputStream
스트림은 바이트단위로 데이터를 전송하며 입출력 대상에 따라 여러 종류의 입출력 스트림이 있다. 어떠한 대상에 대해서 작업을 할 것인지 그리고 입력을 할 것인지 출력을 할 것인지에 따라서 해당 스트림을 선택해서 사용하면 된다.
이러한 스트림들은 모두 InputStream
또는 OutputStream
의 자손들이며, 각각 읽고 쓰는데 필요한 추사엠서드를 자신에 맞게 구현해 놓았다.
자바에서는 java.io
패키지를 통해 많은 종류의 입출력관련 클래스들을 제공하고 있다. 또한 입출력을 처리할 수 있는 표준화된 방법을 제공함으로써 입출력의 대상이 달라져도 동일한 방법으로 입출력이 가능하게 한다.
InputStream | OutputStream |
abstract int read( ) | abstract void write(int b) |
int read(byte[ ] b) | void write(byte[ ] b) |
int read(byte[ ] b, int off, int len) | void write(byte[ ] b, int off, int len) |
💡 read()의 반환타입이 byte가 아닌 int인 이유는 read()의 반환값의 범위가 0 ~ 255와 -1이기 때문이다.
InputStream의 read()와 OutputStream의 write(int b)는 입출력의 대상에 따라 읽고 쓰는 방법이 다를 것이기 때문에 각 상황에 알맞게 구현하라는 의미에서 추상메서드로 정의되어 있다. read()와 write(int b)를 제외한 나머지 메서드들은 read()와 write(int b)를 이용해서 구현한 것들이다.
InputStream의 실제 코드를 살펴보면 read(byte[] b, int off, int len)
에서 read()를 호출하는 것을 볼 수 있다.
메서드는 선언부만 알고 있어도 호출이 가능하기 때문에, 추상메서드를 호출하는 코드를 작성할 수 있다. 결론적으로 read()는 반드시 구현되어야하는 핵심적인 메서드로, read()가 없으면 나머지 메서드들이 의미가 없다는 것이다.
보조 스트림
스트림의 기능을 보완하기 위한 보조스트림들이 제공되는데, 보조스트림은 실제 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력할 수 있는 기능은 없다. 하지만 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있다.
FileInputStream fis = new FileInputStream("test.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
bis.read();
위 코드는 test.txt라는 파일을 읽는 예제이다. 코드 상으로는 보조스트림인 BufferedInputStream이 입력기능을 수행하는 것처럼 보이지만, 실제 입력기능은 BufferedInputStream과 연결된 FileInputStream이 수행하고, 보조스트림인 BufferedInputStream은 버퍼만을 제공한다.
모든 보조스트림 역시 InputStream과 OutputStream의 자손들이므로 입출력방법이 같다.
입력 | 출력 | 설명 |
FilterInputStream | FilterOutputStream | 필터를 이용한 입출력 처리 |
BufferedInputStream | BufferedOutputStream | 버퍼를 이용한 입출력 성능향상 |
DataInputStream | DataOutputStream | int, float와 같은 기본형 단위(primitive type)로 데이터를 처리하는 기능 |
SequenceInputStream | 없음 | 두 개의 스트림을 하나로 연결 |
LineNumberInputStream | 없음 | 읽어 온 데이터의 라인 번호를 카운트 (JDK 1.1부터 LineNumberReader로 대체) |
ObjectInputStream | ObjectOutputStream | 데이터를 객체단위로 읽고 쓰는데 사용. 주로 파일을 이용하며 객체 직렬화와 관련있음 |
없음 | PrintStream | 버퍼를 이용하며, 추가적인 print 관련 기능 (print, printf, println 메서드) |
PushbackInputStream | 없음 | 버퍼를 이용해서 읽어 온 데이터를 다시 되돌리는 기능 (unread, push back to buffer) |
Byte와 Character 스트림
Byte 스트림은 입출력의 단위가 1byte이다. 자바에서는 한 문자를 의미하는 char형이 2byte이다. 따라서 바이트 스트림으로는 문자를 처리하기 어렵다. 이를 보완하기 위해서 제공되는 Character 스트림, 문자기반 스트림은 입출력의 단위가 2byte이다. 문자데이터를 입출력할 때는 바이트 스트림 대신 문자 스트림을 사용하면 된다.
Byte 스트림
InputStream과 OutputStream은 모든 바이트 스트림의 조상이다. 각각 지원하는 메서드들은 공식 문서를 참고하자.
스트림의 종류에 따라 mark()와 reset()을 사용하여 이미 읽은 데이터를 되돌려서 다시 읽을 수 있다. markSupported()를 통해서 이 기능을 지원하는 스트림인지 확인할 수 있다.
flush()는 버퍼가 있는 출력스트림의 경우에만 의미가 있으며, OutputStream에 정의된 flush()는 아무런 일도 하지 앟는다.
프로그램이 종료될 때, 사용하고 닫지 않은 스트림을 JVM이 자동적으로 닫아 주기는 하지만, 스트림을 사용해서 모든 작업을 마치고 난 후에는 close()를 호출해서 반드시 닫아 주어야 한다. 그러나 ByteArrayInputStream
과 같이 메모리를 사용하는 스트림과 System.in
, System.out
과 같은 표준 입출력 스트림은 닫아 주지 않아도 된다.
▶ ByteArrayInputStream과 ByteArrayOutputStream
ByteArrayInputStream과 ByteArrayOutputStream은 메모리, 즉 바이트배열에 데이터를 입출력하는데 사용되는 스트림이다. 주로 다른 곳에 입출력하기 전에 데이터를 임시로 바이트배열에 담아서 변환 등의 작업을 하는데 사용된다.
▶ ByteArrayIOStream 예제
package me.gracenam.study.week13;
import java.io.*;
import java.util.Arrays;
public class ioExam01 {
public static void main(String[] args) {
byte[] inSrc = {0, 1, 2, 3, 4, 5};
byte[] outSrc = null;
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
int data = 0;
while((data = input.read()) != -1) {
output.write(data);
}
outSrc = output.toByteArray();
System.out.println("Input Source : " + Arrays.toString(inSrc));
System.out.println("Output Source : " + Arrays.toString(outSrc));
}
}
▶ 배열을 사용한 예제
package me.gracenam.study.week13;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
public class ioExam02 {
public static void main(String[] args) {
byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
byte[] outSrc = null;
byte[] tmp = new byte[10];
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
input.read(tmp, 0, tmp.length);
output.write(tmp, 5, 5);
outSrc = output.toByteArray();
System.out.println("Input Source : " + Arrays.toString(inSrc));
System.out.println("tmp : " + Arrays.toString(tmp));
System.out.println("Output Source : " + Arrays.toString(outSrc));
}
}
▶ FileInputStream과 FileOutputStream
FileInputStream과 FileOutputStream은 파일에 입출력을 하기 위한 스트림으로 실제로 가장 많이 사용되는 스트림 중 하나이다.
생성자 | 설명 |
FileInputStream(String name) | 지정된 파일이름(name)을 가진 실제 파일과 연결된 FileInputStream을 생성한다. |
FileInputStream(File file) | 지정된 파일이름을 가진 실제 파일과 연결된 FileInputStream을 생성한다. 단, 파일의 이름이 String이 아닌 File 인스턴스로 지정해주어야 한다. |
FileInputStream(FileDescriptor fdObj) | 파일 디스크립터(fdObj)로 FileInputStream을 생성한다. |
FileOutputStream(String name) | 지정된 파일이름(name)을 가진 실제 파일과 연결된 FileOutputStream을 생성한다. |
FileOutputStream(String name, boolean append) | 지정된 파일이름(name)을 가진 실제 파일과 연결된 FileOutputStream을 생성한다. 두 번째 인자인 append를 true로 하면, 출력 시 기존의 파일내용의 마지막을 덧붙인다. false면 기존의 파일내용을 덮어쓴다. |
FileOutputStream(File file) | 지정된 파일이름을 가진 실제 파일과 연결된 FileOutputStream을 생성한다. 단, 파일의 이름이 String이 아닌 File 인스턴스로 지정해주어야 한다. |
FileOutputStream(File file, boolean append) | 지정된 파일이름을 가진 실제 파일과 연결된 FileOutputStream을 생성한다. 단, 파일의 이름이 String이 아닌 File 인스턴스로 지정해주어야 한다. 두 번째 인자인 append를 true로 하면, 출력 시 기존의 파일내용의 마지막을 덧붙인다. false면 기존의 파일내용을 덮어쓴다. |
FileOutputStream(FileDescriptor fdObj) | 파일 디스크립터(fdObj)로 FileOutputStream을 생성한다. |
▶ 파일 입출력 예제
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream(args[0]);
FileOutputStream fos = new FileOutputStream(args[1]);
int data = 0;
while((data = fis.read()) != -1) {
fos.write(data);
}
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
예제에서는 FileInputStream과 FileOutputStream, 즉 바이트 스트림을 사용했지만 이처럼 텍스트파일을 다루는 경우에는 문자 스트림인 FileReader와 FileWriter를 사용하는 것이 좋다.
Character 스트림
문자데이터를 다루는데 사용된다는 점을 제외하고 바이트 스트림과 사용방법은 거의 동일하다.
바이트 스트림에서는 InputStream / OutputStream이 조상이라면 문자 스트림에서는 Reader와 Writer가 그와 같은 역할을 한다.
💡문자기반 스트림이라는 것이 단순히 2 byte로 스트림을 처리하는 것만을 의미하지 않는다. 문자 데이터를 다루는데 필요한 또 하나의 정보는 인코딩(encoding)이다.
문자 스트림은 여러 종류의 인코딩과 자바에서 사용하는 유니코드(UTF-16)간의 변환을 자동적으로 처리해준다.
▶ FileReader와 FileWriter
FileInputStream과 FileOutputStream과 마찬가지로 파일로부터 텍스트를 읽고, 파일에 쓰는데 사용된다. 사용방법은 동일하다.
▶ FileReader/Writer 예제
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderEx01 {
public static void main(String[] args) {
try {
String fileName = "test.txt";
FileInputStream fis = new FileInputStream(fileName);
FileReader fr = new FileReader(fileName);
int data = 0;
while((data = fis.read()) != -1) {
System.out.print((char)data);
}
System.out.println();
fis.close();
while((data = fr.read()) != -1)
System.out.print((char)data);
System.out.println();
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
결과를 보면 알 수 있듯이 FileInputStream은 한글이 깨져서 출력되었다.
▶PipedReader와 PipedWriter
PipedReader / PipedWriter는 쓰레드 간에 데이터를 주고받을 때 사용된다. 다른 스틺과 달리 입력과 출력 스트림을 하나의 스트림으로 연결해서 데이터를 주고받는다는 특징이 있다.
▶ PipedReader/Writer 예제
package me.gracenam.study.week13;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.StringWriter;
class InputThread extends Thread {
PipedReader input = new PipedReader();
StringWriter sw = new StringWriter();
InputThread(String name) {
super(name);
}
public void run() {
try {
int data = 0;
while((data = input.read()) != -1) {
sw.write(data);
}
System.out.println(getName() + " received : " + sw.toString());
} catch (IOException e) {}
}
public PipedReader getInput() {
return input;
}
public void connect(PipedWriter output) {
try {
input.connect(output);
} catch (IOException e) {
}
}
}
class OutputThread extends Thread {
PipedWriter output = new PipedWriter();
OutputThread(String name) {
super(name);
}
public void run() {
try {
String msg = "This is a msg";
System.out.println(getName() + " sent : " + msg);
output.write(msg);
output.close();
} catch (IOException e) {}
}
public PipedWriter getOutput() {
return output;
}
public void connect(PipedReader input) {
try {
output.connect(input);
} catch (IOException e) {}
}
}
public class PipedEx {
public static void main(String[] args) {
InputThread inThread = new InputThread("InputThread");
OutputThread outThread = new OutputThread("OutputThread");
inThread.connect(outThread.getOutput());
inThread.start();
outThread.start();
}
}
문자 보조스트림
▶ BufferedReader와 BufferedWriter
버퍼를 이용해서 입출력의 효율을 높일 수 있도록 해주는 역할을 한다. BufferedReader는 readLine()을 사용하면 데이터를 라인단위로 읽을 수 있고, BufferedWriter는 줄바꿈을 해주는 newLine()이라는 메서드를 가지고 있다.
▶ InputStreamReader와 OutputStreamWriter
이름에서 알 수 있듯이 바이트 스트림을 문자 스트림으로 연결시켜주는 역할을 한다. 그리고 바이트 스트림의 데이터를 지정된 인코딩의 문자데이터로 변환하는 작업을 수행한다. 이 둘은 각각 Reader/Writer의 자손이다.
▶ InputStreamReader의 생성자와 메서드
생성자 / 메서드 | 설명 |
InputStreamReader(InputStream in) | OS에서 사용하는 기본 인코딩의 문자로 변환하는 InputStreamReader를 생성한다. |
InputStreamReader(InputStream in, String encoding) | 지정된 인코딩을 사용하는 InputStreamReader를 생성한다. |
String getEncoding() | InputStreamReader의 인코딩을 알려 준다. |
▶ OutputStreamWriter의 생성자와 메서드
생성자 / 메서드 | 설명 |
OutputStreamWriter(OutputStream out) | OS에서 사용하는 기본 인코딩의 문자로 변환하는 OutputStreamWriter를 생성한다. |
OutputStreamWriter(OutputStream out, String encoding) | 지정된 인코딩을 사용하는 OutputStreamWriter를 생성한다. |
String getEncoding() | OutputStreamWriter의 인코딩을 알려 준다. |
표준 스트림
표준 입출력 - System.in, System.out, System.err
표준 입출력은 콘솔(console)을 통한 데이터 입력과 콘솔로의 데이터 출력을 의미한다. 자바에서는 표준 입출력(standard I/O)을 위해 3가지 입출력 스트림(System.in, System.out, Ststem.err)를 제공하는데, 이들은 자바 어플리케이션의 실행과 동시에 사용할 수 있게 자동적으로 생성된다. 지금까지 System.out을 스트림의 생성없이 사용할 수 있었던 것이 바로 이러한 이유 때문이다.
System클래스의 소스를 보면 알 수 있듯이 in, out, err는 System클래스에 선언된 클래스변수(static변수)이다.
File
자바에서는 File클래스를 통해서 파일과 디렉토리를 다룰 수 있도록 하고 있다. 그래서 File인스턴스는 파일 일 수도 있고 디렉토리일 수도 있다.
▶ File의 생성자와 경로와 관련된 메서드
생성자 / 메서드 | 설명 |
File(String fileName) | 주어진 문자열(fileName)을 이름으로 가지는 파일을 위한 File인스턴스를 생성한다. 파일 뿐만 아니라 디렉토리도 같은 방법으로 다룬다. fileName은 주로 경로(path)를 포함해서 지정해주지만, 파일 이름만 사용해도 되는데 이 경우 프로그램이 실행되는 위치가 경로(path)로 간주된다. |
File(String pathName, String fileName) File(File pathName, String fileName) |
파일의 경로와 이름을 따로 구분해서 지정할 수 있도록 한 생성자이다. 두 번째 것은 경로를 문자열이 아닌 File인스턴스인 경우를 위해서 제공된 것이다. |
File(URI uri) | 지정된 uri로 파일을 생성한다. |
String getName() | 파일이름을 String으로 반환한다. |
String getPath() | 파일의 경로(path)를 String으로 반환한다. |
String getAbsolutePath() File getAbsolutePath() |
파일의 절대경로를 String으로 반환한다. 파일의 절대경로를 File로 반환한다. |
String getParent() File getParent() |
파일의 조상 디렉토리를 String으로 반환한다. 파일의 조상 디렉토리를 File로 반환한다. |
String getCanonicalPath() File getCanonicalPath() |
파일의 정규경로를 String으로 반환한다. 파일의 정규경로를 File로 반환한다. |
▶ 경로와 관련된 File의 멤버변수
멤버변수 | 설명 |
static String pathSeparator | OS에서 사용하는 경로(path) 구분자. 윈도우 ";", 유닉스 ":" |
static char pathSeparatorChar | OS에서 사용하는 경로(path) 구분자. 윈도우 ";", 유닉스 ":" |
static String separator | OS에서 사용되는 이름 구분자. "\", 유닉스 "/" |
static char separatorChar | OS에서 사용되는 이름 구분자. "\", 유닉스 "/" |
NIO
NIO(New Input/Output)
NIO는 기존 I/O에 대한 속도 단점을 개선하기 위해 자바4부터 추가된 패키지로, 의미 그대로 새로운 입출력이라는 의미를 가진다. 현재는 자바 7버전에서 한번 더 버전업하여 NIO.2 API와 함께 제공되고 있는 패키지다. (NIO.2 API는 자바 IO와 자바 NIO 사이의 일관성 없는 클래스 설계를 바로 잡고, 비동기 채널등의 네트워크 지원을 대폭 강화하였다.)
NIO 패키지 | 내용 |
java.nio | 다양한 버퍼 클래스 |
java.nio.channels | 파일 채널, TCP 채널, UDP 채널 등의 클래스 |
java.nio.channels.spi | java.nio.channels 패키지를 위한 서비스 제공자 클래스 |
java.nio.charset | 문자셋, 인코더, 디코더 API |
java.nio.charset.spi | java.nio.charset 패키지를 위한 서비스 제공자 클래스 |
java.nio.file | 파일 및 파일 시스템에 접근하기 위한 클래스 |
java.nio.file.attribute | 파일 및 파일 시스템의 속성에 접근하기 위한 클래스 |
java.nio.file.spi | java.nio.file 패키지를 위한 서비스 제공자 클래스 |
기존 IO는 왜 느린가?
자바에서 I/O를 처리하는 구조는 크게 유저영역과 커널영역, 2가지 영역으로 나눌 수 있다.
유저 영역은 실행 중인 프로그램이 존재하는 제한된 영역(하드웨어에 직접 접근 불가)이고 커널 영역은 하드웨어에 직접 접근이 가능하고 다른 프로세스를 제어할 수 있는 영역이다.
※ 자바 I/O 프로세스
- 프로세스가 커널에 파일 읽기 명령을 내린다.
- 커널은 시스템 콜[read()]을 사용해 디스크 컨트롤러가 물리적 디스크로부터 읽어온 파일 데이터를 커널 영역안의 버퍼에 쓴다.
- DMA(Direct Memory Access) : CPU의 도움없이 물리적 디스크에서 커널영역의 버퍼로 데이터를 읽어오는 것
- 모든 파일 데이터가 버퍼에 복사되면 다시 프로세스 안의 버퍼로 복사한다.
- 프로세스 안의 버퍼의 내용으로 프로그래밍한다.
이러한 I/O 프로세스에서 첫번째 문제는 3)의 과정이 너무나 비효율적이라는 것이다. 왜냐하면 커널안의 버퍼 데이터를 프로세스 안으로 다시 복사하기 때문이다. 그렇다면 만약 3)의 과정을 없애고 커널영역에 바로 접근할 수 있다면 어떻게 될까? 만약 이게 가능하다면 우리는 버퍼를 복사하는 CPU를 낭비하지도 , GC관리를 따로 하지 않아도 I/O를 사용할 수 있게 됩니다.
이런 첫 번째 문제도 심각하지만 두번째 문제 역시 심각하다. 두 번째 문제는 I/O프로세스를 거치는 동안 작업을 요청한 쓰레드가 블록킹된다는 것이다.
이 두 가지 외에도 많은 문제가 존재하겠지만, NIO는 위와 같은 문제를 버퍼/채널 등을 통해 해결했다.
IO와 NIO의 차이점
IO와 NIO는 데이터를 입출력한다는 목적은 동일하지만, 방식에서 큰 차이가 나타난다.
구분 | IO | NIO |
입출력 방식 | 스트림 방식 | 채널 방식 |
버퍼 방식 | 넌버퍼(non-buffer) | 버퍼(buffer) |
비동기 방식 | 지원 안 함 | 지원 |
블로킹 / 넌블로킹 방식 | 블로킹 방식만 지원 (동기) | 블로킹 / 넌블로킹 모두 지원 (동기 / 비동기) |
스트림과 채널 (Stream vs Channel)
IO는 스트림(Stream) 기반이다.
스트림은 입력 스트림과 출력 스트림으로 구분되어 있기 때문에 데이터를 읽기 위해서는 입력 스트림을 생성해야 하고, 데이터를 출력하기 위해서는 출력 스트림을 생성해야 한다.
NIO는 채널(Channel) 기반이다.
채널은 스트림과 달리 양방향으로 입력과 출력이 가능하다.
그렇기 때문에 입력과 출력을 위한 별도의 채널을 만들 필요가 없다.
넌버퍼와 버퍼 (non-buffer vs buffer)
IO에서는 출력 스트림이 1바이트를 쓰면 입력 스트림이 1바이트를 읽는다. 이러한 시스템은 대체로 느리다. 이것보다는 버퍼(Buffer : 메모리 저장소)
를 사용해서 복수 개의 바이트를 한꺼번에 입력받고 출력하는 것이 성능에 이점을 가지게 된다. 그래서 IO는 버퍼를 제공해주는 보조 스트림인 BufferedInputStream, BufferedOutputStream을 연결해 사용하기도 한다.
NIO는 기본적으로 버퍼를 사용해서 입출력을 하기 때문에 IO보다 높은 성능을 가진다.
IO는 스트림에서 읽은 데이터를 즉시 처리한다.
- 스트림으로부터 입력된 전체 데이터를 별도로 저장하지 않으면, 입력된 데이터의 위치를 이동해 가면서 자유롭게 이용할 수 없다.
NIO는 읽은 데이터를 무조건 버퍼에 저장한다.
- 버퍼 내에서 데이터의 위치 이동을 해가면서 필요한 부분만 읽고 쓸 수 있다.
블로킹과 넌블로킹 (Blocking vs non-blocking)
IO는 블로킹(Blocking) 된다.
입력 스트림의 read() 메소드를 호출하면 데이터가 입력되기 전까지 Thread는 블로킹(대기상태)가 된다. 마찬가지로 출력 스트림의 write() 메소드를 호출하면 데이터가 출력되기 전까지 Thread는 블로킹된다.
IO Thread가 블로킹되면 다른 일을 할 수 없고 블로킹을 빠져나오기 위해 인터럽트(interrupt)도 할 수 없다.
→ 블로킹을 빠져나오는 유일한 방법은 스트림을 닫는것이다.
NIO는 블로킹과 넌블로킹(non-blocking) 특징을 모두 가진다.
IO블로킹과 NIO 블로킹과의 차이점은 NIO 블로킹은 Thread를 인터럽트(interrupt) 함으로써 빠져나올 수 있다. 블로킹의 반대개념이 넌블로킹인데, 입출력 작업 시 Thread가 블로킹되지 않는 것을 말한다.
NIO의 넌블로킹은 입출력 작업 준비가 완료된 채널만 선택해서 작업 Thread가 처리하기 때문에 작업 Thread가 블로킹되지 않는다.
→ 작업준비가 완료되었다는 뜻은 지금 바로 읽고 쓸수 있는 상태를 말한다.
NIO 넌블로킹의 핵심 객체는 멀티플렉서(multiplexor)인 셀릭터(Selector)이다. 셀렉터는 복수 개의 채널 중에서 준비 완료된 채널을 선택하는 방법을 제공해준다.
버퍼(Buffer)
위에서 주구장창 나온 버퍼는 뭘까? 버퍼는 읽고 쓰기가 가능한 메모리 배열이다.
Buffer 종류
Buffer는 저장되는 데이터 타입에 따라 분류될 수 있고, 어떤 메모리를 사용하느냐에 따라 종류가 구분될 수 있다.
- 다이렉트(Direct)
- 넌다이렉트(NonDirect)
데이터 타입에 따른 버퍼
저장되는 데이터 타입에 따라서 별도의 클래스로 제공된다. 이 버퍼 클래스들은 Buffer 추상 클래스를 모두 상속하고 있다.
▶ 버퍼 클래스
넌다이렉트와 다이렉트 버퍼
버퍼가 사용하는 메모리 위치에 따라서 넌다이렉트(non-direct) 버퍼와 다이렉트(direct) 버퍼로 분류된다.
넌다이렉트 버퍼는 JVM이 관리하는 힙 메모리 공간
을 이용하는 버퍼이고, 다이렉트 버퍼는 운영체제가 관리하는 메모리 공간
을 이용하는 버퍼이다.
넌다이렉트 버퍼는 JVM 힙 메모리를 사용하므로 생성 시간이 빠르지만, 다이렉트 버퍼는 운영체제의 메모리를 할당받기 위해 운영체제의 네이티브(native) C함수를 호출해야 하고 여러가지 잡다한 처리를 해야하므로 상대적으로 생성이 느리다. 그렇기 때문에 다이렉트 버퍼는 자주 생성하기보단 한 번 생성해놓고 재사용하는 것이 유리하다.
또한 넌다이렉트 버퍼는 JVM의 제한된 힙 메모리를 사용하므로 버퍼의 크기를 크게 잡을 수 없고, 다이렉트 버퍼는 운영체제가 관리하는 메모리를 사용하므로 운영체제가 허용하는 범위 내에서 대용량 버퍼를 생성시킬 수 있다.
Reference
Comments
JAVA STUDY HALLE 의 다른 글
-
15주차 피드백 06 Mar 2021
-
15주차 과제: 람다식. 28 Feb 2021
-
14주차 피드백 27 Feb 2021
-
14주차 과제: 제네릭. 22 Feb 2021
-
13주차 피드백 20 Feb 2021
-
13주차 과제: I/O. 08 Feb 2021
-
12주차 피드백 06 Feb 2021
-
12주차 과제: 애노테이션. 01 Feb 2021
-
11주차 피드백 30 Jan 2021
-
11주차 과제: Enum. 24 Jan 2021
-
10주차 피드백 23 Jan 2021
-
10주차 과제: 멀티쓰레드 프로그래밍. 18 Jan 2021
-
9주차 피드백 16 Jan 2021
-
9주차 과제: 예외 처리. 10 Jan 2021
-
8주차 피드백 09 Jan 2021
-
8주차 과제: 인터페이스. 03 Jan 2021
-
7주차 피드백 02 Jan 2021
-
7주차 과제: 패키지. 28 Dec 2020
-
6주차 피드백 27 Dec 2020
-
6주차 과제: 상속. 21 Dec 2020
-
5주차 피드백 20 Dec 2020
-
5주차 과제: 클래스. 14 Dec 2020
-
4주차 피드백 13 Dec 2020
-
4주차 과제: 제어문. 30 Nov 2020
-
3주차 피드백 29 Nov 2020
-
3주차 과제: 연산자. 22 Nov 2020
-
2주차 피드백 21 Nov 2020
-
2주차 과제: 자바 데이터 타입, 변수 그리고 배열. 15 Nov 2020
-
1주차 피드백 15 Nov 2020
-
1주차 과제: JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가. 08 Nov 2020