I/O
목차
- 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
- InputStream과 OutputStream
- Byte와 Character 스트림
- 표준 스트림 (System.in, System.out, System.err)
- 파일 읽고 쓰기
I/O 입출력
입출력이란?
입출력(I/O)란 Input과 Output의 약자로 입력과 출력, 간단히 입출력이라 한다.
입출력은 컴퓨터 내부 또는 외부 장치와 프로그램 간의 데이터를 주고받는 것을 말한다.
자바 NIO(New I/O)
자바 1.4 버전부터 추가된 API로 넌블로킹 처리가 가능하며, 스트림이 아닌 채널을 사용한다.
채널기반의 입출력방식을 사용하며 단방향이 아닌 양방향으로 입출력이 가능하다.
IO vs NIO
- IO의 방식으로 각각의 스트림에서 read()와 write()가 호출이 되면 데이터가 입력되고, 데이터가 출력되기 전까지, 스레드는 블로킹(멈춤) 상태가 된다. 이렇게 되면 작업이 끝날 때까지 기다려야 하며, 그 이전에는 해당 IO 스레드는 사용할 수 없게 되고, 인터럽트도 할 수 없다. 블로킹을 빠져나오려면 스트림을 닫는 방법 밖에 없다.
- NIO의 블로킹 상태에서는 Interrupt를 이용하여 빠져나올 수 있다.
구분 IO NIO 입출력 방식 스트림 채널 버퍼 방식 Non-Buffer Buffer 비동기 방식 지원 X O Blocking/Non-Blocking 방식 Blocking Only Both 사용 케이스 연결 클라이언트가 적고 IO 가 큰 경우 연결 클라이언트가 많고 IO 처리가 작은 경우
스트림 (Stream)
FIFO(First In First Out)
단방향이기 때문에 입력 스트림과 출력 스트림을 별도로 사용해야 한다.
연속된 데이터의 흐름으로 입출력 진행 시 다른 작업을 할 수 없는 블로킹상태가 된다.
입출력 대상을 변경하기 편하며 동일한 프로그램 구조를 유지할 수 있다.
버퍼 (Buffer)
- byte, char, int 등 기본 데이터 타입을 저장할 수 있는 저장소로서, 배열과 마찬가지로 제한된 크기(capacity)에 순서대로 데이터를 저장한다.
- 버퍼는 데이터를 저장하기 위한 것이지만, 실제로 버퍼가 사용되는 것은 채널을 통해서 데이터를 주고받을 때 쓰인다.
- 채널을 통해서 소켓, 파일 등에 데이터를 전송할 때나 읽어올 때 버퍼를 사용하게 됨으로써 가비지량을 최소화할 수 있게 되며, 이는 가바지 컬렉션 회수를 줄임으로써 서버의 전체 처리량을 증가시켜 준다.
채널 (Channel) 기반의 I/O
- 데이터가 통과하는 쌍방향 통로이며, 채널에서 데이터를 주고받을 때 사용되는 것이 버퍼이다.
- 채널에는 소켓과 연결된 SocketChannel, 파일과 연결된 FileChannel, 파이프와 연결된 Pipe.SinkChannel과 Pipe.SourceChannel 등이 존재하며, 서버소켓과 연결된 ServerSocketChannel 도 존재한다.
InputStream과 OutputStream
InputStream과 OutputStream은 Java Stream의 부모들이다
InputStream
- 바이트 기반의 입력 스트림의 최상위 클래스(추상 클래스)이다.
- 모든 바이트 기반 입력 스트림은 이 클래스를 상속받아서 만들어진다.
- 버퍼, 파일, 네트워크 단에서 입력되는 데이터를 읽어오는 기능을 수행한다.
메소드들
- int available() : 현재 읽을 수 있는 바이트 수를 반환
- void close() : 현재 열려있는 InputStream을 닫는다
- void mark(int readlimit) : InputStream에서 현재의 위치를 표시
- boolean markSupported() : 해당 InputStream에서 mark()로 지정된 지점이 있는지에 대한 여부를 확인
- abstract int read() : InputStream에서 한 바이트를 읽어서 int 값으로 반환
- int read(byte[] b) : 인자만큼의 데이터를 읽어서 b에 저장하고 읽은 바이트 수 반환
- int read(byte b, int off, int len) : len만큼 읽어서 b의 off위치에 저장하고 읽은 바이트 수 반환
- void reset() : mark()를 마지막으로 호출한 위치로 이동
- long skip(long n) : InputStream에서 n바이트만큼 데이터를 스킵하고 바이트 수를 반환
OutputStream
- 바이트 기반의 입력 스트림의 최상위 클래스(추상 클래스)이다.
- 모든 바이트 기반 출력 스트림은 이 클래스를 상속받아서 만들어진다.
- 버퍼, 파일, 네트워크 단으로 데이터를 내보내는 기능을 수행한다.
메소드들
- void close() : OutputStream을 닫는다
- void flush() : 버퍼에 남아있는 출력 스트림을 출력
- void write(byte[] b) : 버퍼의 내용을 출력
- void write(byte[] b, int off, int len) : b 배열 안에 있는 시작 off부터 len만큼 출력
- abstract void write(int b) : 정수 b의 하위 1바이트를 출력
Byte와 Character 스트림
앞서서 살펴본 InputStream과 OutputStream 은 대표적인 ByteStream이다. ByteStream 은 이름처럼 1바이트 단위로 데이터를 입출력한다.
그에 반해 CharacterStream 은 2바이트 단위로 데이터를 전송하는데, 자바는 기본적으로 character 값들이 유니코드 규약에 맞춰져 있다. 그리고 유니코드는 기본단위가 2바이트임으로 문자를 입출력할 때, CharacterStream을 쓰는 것이 가장 적절하다고 할 수 있다.
CharacterStream 은 클래스명에 Reader, Writer 가 들어가 있다.
표준 스트림 (System.in, System.out, System.err)
표준 스트림
프로그램과 환경(키보드, 모니터, 콘솔..) 사이에 연결된 입출력 스트림을 의미한다.
Java 컨텍스트에서 표준 입력/출력 스트림(System.in
, System.out
및 System.err
)은 JVM(Java Virtual Machine)과 연결되며 JVM이 실행 중일 때 자동으로 사용 가능하다. 초기화 프로세스 중에 JVM에 의해 열리고 구성되며 명시적으로 만들 필요가 없다.
System.in
콘솔로부터의 입력을 받는 표준 스트림을 가리키는 상수이다. 해당객체는 JVM 이 메모리에 올라갈 때 생성된다고 한다. InputStream으로 byte 단위로 입력을 받을 수 있다.
System.out.print("입력 : ");
int input = System.in.read();
System.out.println("출력 : " + (char)input);
// ----
입력 : h
출력 : h
// ----
입력 : 안
출력 : ì
InputStream 은 ByteStream으로 한글을 입력받는 데 있어서 어려움이 있다.
한글은 유니코드로 2byte 단위이기 때문에 InputStream으로는 1바이트 밖에 읽지 못한다.
이 경우에는 CharacterStream 인 InputStreamReader을 활용할 수 있다.
System.out.print("입력 : ");
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
int input = inputStreamReader.read();
System.out.println("출력 : " + (char)input);
// ----
입력 : 안
출력 : 안
만약 2byte가 넘는 데이터를 읽고 싶다면, BufferedReader를 활용할 수도 있다.
System.out.print("입력 : ");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String inputData = bufferedReader.readLine();
System.out.println("출력 : " + inputData);
// ----
입력 : 안녕하세요
출력 : 안녕하세요
혹은, Scanner를 사용할 수 도 있다.
System.out.print("입력 : ");
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
System.out.println("출력 : " + inputData);
// ----
입력 : 안녕하세요
출력 : 안녕하세요
System.out
콘솔로 출력을 하는 표준 스트림을 가리키는 상수이다.
대표적인 예시 System.out.println()
System.err
표준 에러 출력 장치를 가리키는 상수이다
파일 읽고 쓰기
- 텍스트 파일인 경우 문자 스트림 클래스들을 사용하면 되고, 바이너리 파일인 경우 바이트 스트림을 기본적으로 사용한다.
- 입출력 효율을 위해 Buffered 계열의 보조 스트림을 함께 사용하는 것이 좋다.
- 텍스트 파일
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
String s;
while ((s = br.readLine()) != null) {
bw.write(s + "\n");
}
- 이진 파일
BufferedInputStream is = new BufferedInputStream(new FileInputStream("a.jpg"));
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream("b.jpg"));
byte[] buffer = new byte[16384];
while (is.read(buffer) != -1) {
os.write(buffer);
}
Reference
'Java' 카테고리의 다른 글
[Java] 람다식 (0) | 2023.09.25 |
---|---|
[Java] 제네릭(Generic) (0) | 2023.08.07 |
[Java] 어노테이션(Annotation) (0) | 2023.07.04 |
[Java] 리플렉션(Reflection) (0) | 2023.06.30 |
[Java] 바이트코드 조작 (0) | 2023.06.23 |
댓글