-
[스터디 13주차] I/O프로그래밍 언어/JAVA 2021. 7. 20. 17:51더보기
목표: 자바의 Input과 Ontput에 대해 학습
1. 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
스트림(stream)은 어느 한 쪽에서 다른 쪽으로 데이터를 전달할수 있도록 해주는 연결 통로같은 것이다.
스트림은 단방향통신만 가능하기때문에 입력과 출력을 동시에 처리하기 위해서는 입력을 위한 스트림과 출력을 위한 스트림, 2개의 스트림이 있어야한다.
채널(Channel)은 스트림과 달리 양방향으로 입력과 출력이 가능하다.
버퍼(buffer)는 byte, char, int 등 기본 데이터 타입을 저장할 수 있는 저장소로서, 배열과 마찬가지로 제한된 크기에 순서대로 데이터를 저장한다. 버퍼는 데이터를 저장하기 위한 것이지만 실제로 버퍼가 사용되는 것은 채널을 통해서 데이터를 주고 받을 때 쓰인다.
여기서, 스트림/ 버터/ 채널 기반의 I/O를 알아보기 위해 IO와 NIO를 비교하면 좋을 것같다.
IO는 데이터와 입력과 출력을 다루는 것으로 자바에서 java.io 패키지로 IO API를 제공한다.
NIO는 자바 1.4부터 추가된 것으로 새로운 입출력이라는 뜻으로 java.nio 패키지로 API를 제공한다.
IO와 NIO의 차이점은 아래의 표와 같다.
구분 IO NIO 입출력 방식 스트림 채널 버퍼 non-buffer buffer 비동기 방식 지원 X 지원 블로킹/ 넌블로킹 블로킹만 지원 둘다 지원 먼저, 입출력 방식을 보면 IO는 스트림 기반이고 NIO는 채널 기반이다.
스트림 기반은 데이터를 주고 받는 통로를 스트림을 기반으로 하여 입력 스트림과 출력 스트림으로 구분되어 사용된다.
채널 기반은 스트림과 달리 양방향으로 입력과 출력이 가능하여 입력과 출력을 위한 별도의 채널을 만들 필요가 없다.
다음으로 버퍼사용여부를 보면 IO는 버퍼를 사용하지 않는 non-buffer방식이고 NIO는 버퍼를 사용하는 buffer방식이다.
non-buffer인 IO는 출력 스트림이 1바이트를 쓰면 입력 스트림이 1바이트를 읽는 구조로 대체로 느리다.
buffer를 쓰는 NIO는 복수 개의 바이트를 한꺼번에 입력받아 버퍼에 저장한 뒤 출력하는 구조이다.
간단한 예시를 들면, 왼쪽에서 1L의 물을 받아 오른쪽으로 옮긴다고 생각해보자.
non-buffer는 1ml가 되면 왼쪽에서 오른쪽으로 옮기는 과정을 반복한다.
하지만 buffer는 버퍼라는 통에 1L를 받은 후에 한번에 왼쪽에서 오른쪽으로 옮긴다.
이렇게 보면 성능의 차이가 발생할 수 있는 것을 볼 수 있다.
또한, non-buffer인 IO는 스트림으로부터 입력된 전체 데이터를 별도로 저장하지 않으면 입력된 데이터의 위치를 이동해 가면서 자유롭게 이용할 수 없다.
반면에 NIO는 버퍼에 데이터를 저장하기에 버퍼 내에서 데이터의 위치 이동을 해가면서 필요한 부분만 읽고 쓸 수 있다.
IO는 버퍼를 제공해주는 보조 스트림인 BufferedInputStream, BufferedOutputStream을 연결해 사용하기도 한다.
마지막으로 블로킹(Blocking)과 논블로킹(non-blocking)을 알아보자.
IO는 블로킹(Blocking)만 지원하여 입력 스트림의 read() 메소드를 호출하면 데이터가 입력되기 전까지 스레드는 블로킹(대기상태)가 된다. 또한, 출력 스트림의 write() 메소드를 호출하면 데이터가 출력되기 전까지 스레드는 블로킹된다.
IO 스레드가 블로킹되면 다른 일을 할 수 없고 블로킹을 빠져나오기 위해 인터럽트(interrupt)도 할 수 없다.
(블로킹을 빠져나오는 유일한 방법은 스트림을 닫는것이다.)
NIO는 블로킹과 넌블로킹(non-blocking)을 모두 지원한다.
여기서 IO블로킹과 NIO 블로킹과의 차이점은 NIO 블로킹은 스레드를 인터럽트(interrupt) 함으로써 빠져나올 수 있다 는 것이다.
논블로킹(non-blocking)은 입출력 작업 시 스레드가 블로킹되지 않는 것을 말한다.
NIO의 논블로킹은 입출력 작업 준비가 완료된 채널만 선택해서 작업 스레드가 처리하기 때문에 작업 스레드가 블로킹되지 않는다.
2. InputStream과 OutputStream
스트림의 종류는 프로그램이 출발지인지 도착지인지에 따라서 결정되는데 프로그램이 데이터를 입력받을 때에는 입력 스트림(InputSteam)이라고 하고 프로그램이 데이터를 보낼 때에는 출력 스트림(OutputStream)이라고 한다.
1) InputStream
InputStream은 바이트 기반 입력 스트림의 최상위 클래스로 추상 클래스이다.
모든 바이트 기반 입력 스트릠은 InputStream 클래스를 상속받아서 만들어진다.
InputStream 클래스의 주요 메소드는 아래의 표와 같다.
리턴타입 메소드 설명 int read() 입력 스트림으로부터 1바이트를 읽고 읽은 바이트를 리턴한다.
더 이상 입력 스트림으로부터 바이트를 읽을 수 없다면 -1을 리턴한다.int read(byte[] b) 입력 스트림으로부터 읽은 바이트들을 매개값으로 주어진 바이트 배열 b에
저장하고 실제로 읽은 바이트 수를 리턴한다.int read(byte[] b, int off, int len) 입력 스트림으로부터 len개의 바이트만큼 읽고 매개값으로 주어진 바이트 배열 b[off]부터 len개까지 저장한다.
실제로 읽은 바이트 수인 len개를 리턴하고 len개를 모두 읽지 못하면 실제로 읽은 바이트 수를 리턴한다.void close() 사용한 시스템 자원을 반납하고 입력 스트림을 닫는다. InputStream 클래스의 실제 코드는 아래와 같다.
public abstract class InputStream{ ... abstract int read(); int read(byte[] b, int off, int len){ ... for(int i = off ; i < off + len ; i++){ // read()를 호출해서 데이터를 읽어서 배열을 채운다. b[i] = (byte)read(); } ... int read(byte[] b){ return read(b, 0, b.length); } } ... }
InputStream을 더 이상 사용하지 않을 경우에는 close( )메소드를 호춯애서 InputStream에서 사용했던 시스템 자원을 풀어준다.
2) OutputStream
OutputStream은 바이트 기반 출력 스트림의 최상위 클래스로 추상 클래스이다.
모든 바이트 기반 출력 스트림 클래스는 OutputStream 클래스를 상속받아서 만들어진다.
OutputStream 클래스의 주요 메소드는 아래의 표와 같다.
리턴 타입 메소드 설명 void write(int b) 매개 변수로 주어진 int 값에서 끝에 있는 1바이트만 출력 스트림으로 보낸다. void write(byte[] b) 출력 스트림으로 매개값으로 주어진 바이트 배열 b의 모든 바이트를 보낸다. void write(byte[] b, int off, int len) 출력 스트림으로 바이트 배열 b에서 b[oof]부터 len개의 바이트를 보낸다. void flush() 버퍼에 잔류하는 모든 바이트를 출력한다. void close() 사용한 시스템 자원을 반납하고 출력 스트림을 닫는다. 여기서 flush()메소드를 더 자세히 알아보자.
출력 스트림에는 내부에 작은 버퍼가 있어 데이터가 출력되기 전에 버퍼에 데이터가 쌓여있다가 순서대로 출력된다.
flush() 메소드는 버퍼에 잔류하고 있는 데이터를 모두 출력시켜서 버퍼를 비우는 역할을 하는 것이다.
프로그램에서 더 이상 출력할 데이터가 없다면 flush() 메소드를 호출하여 버퍼에 남아있는 모든 데이터를 출력되도록 해야 한다.
3. Byte와 Character 스트림
자바의 기본적인 데이터 입출력(IO) API는 java.io 패키지에서 제공하고 있다.
java.io 패키지의 주요 클래스는 아래의 표와 같다.
클래스 설명 File 파일 시스템의 파일 정보를 얻기 위한 클래스 Console 콘솔로부터 문자를 입출력하기 위한 클래스 InputStream/ OutputStream 바이트 단위 입출력을 위한 최상위 입출력 스트림 클래스 FileInputStream/ FileOutputStream
DataInputStream/ DataOutputStream
ObjectInputStream/ ObjectInputStream
PrintStream
BufferInputStream/ BufferOutputStream바이트 단위 입출력을 위한 하위 입출력 스트림 클래스 Reader/ Writer 문자 단위 입출력을 위한 최상위 입출력 스트림 클래스 FileReader/ FileWriter
InputStreamReader/ OutputStreamWriter
PrintWriter
BufferdReader/ BufferdWriter문자 단위 입출력을 위한 하위 스트림 클래스 위 표에서 보면 알겠지만 스트림 클래스는 크게 바이트 기반 스트림과 문자 기반 스트림으로 나뉜다.
바이트 기반 스트림은 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 주고 받을 수 있고
문자 기반 스트림은 오로지 문자만 주고 받을 수 있도록 되어있다.
바이트 기반 스트림은 입출력의 단위가 1byte이고 char형은 2byte이기 때문에 바이트 기반 스트림만으로 문자를 처리하는 데 어려움이 있었다.
그래서 자바에서는 이러한 어려움을 개선하기 위해 문자 기반 스트림을 제공하는 것이다.
위에서 언급했듯이 바이트 기반 스트림 클래스는 InputStream과 OutputStream클래스를 상속받는다.
그리고 문자 기반 스트림 클래스는 Reader와 Writer 클래스를 상속받는다.
그러면 문자 기반 스트림의 최상위 클래스인 Reader와 Writer 클래스에 대해 더 자세히 알아보자.
1) Reader
Reader는 문자 기반 입력 스트림의 최상위 클래스로 모든 문자 기반 입력 스트림은 Reader 클래스를 상속받아서 만들어진다.
Reader 클래스의 주요 메소드는 아래의 표와 같다.
리턴 타입 메소드 설명 int read() 입력 스트림으로부터 한 개의 문자(2bytes)를 읽고 4 바이트(4bytes) int타입으로 리턴한다. int read(char[] cbuf) 입력 스트림으로부터 읽은 문자들을 매개값으로 주어진 문자 배열 cbuf에 저장하고 실제로 읽은 문자 수를 리턴한다. int read(char[] cbuf, int off, int len) 입력 스트림으로부터 len개의 문자를 읽고 매개값으로 주어진 문자 배열 cbuf[off]부터 len개까지 저장하고 실제로 읽은 문자 수인 len개를 리턴한다. void close() 사용한 시스템 자원을 반납하고 입력 스트림을 닫는다. 2) Writer
Writer 클래스는 문자 기반 출력 스트림의 최상위 클래스로 모든 문자 기반 출력 스트림은 Writer 클래스를 상속받아서 만들어진다.
Writer 클래스의 주요 메소드는 아래의 표와 같다.
리턴 타입 메소드 설명 void wirte(int c) 매개값으로 주어진 int값에서 끝에 있는 2바이트만 출력 스트림으로 보낸다. void write(char[] cbuf) 출력 스트림으로 주어진 문자 배열 cbuf의 모든 문자를 보낸다. void write(char[] cbuf, int off, int len) 출력 스트림으로 주어진 문자 배열 cbuf[off]부터 len개까지의 문자를 보낸다. void write(String str) 출력 스트림으로 주어진 문자열을 전부 보낸다. void write(String str, int off, int len) 출력 스트림으로 주어진 문자열 off 순번부터 len개까지의 문자를 보낸다. void flush() 버퍼에 잔류하는 모든 문자열을 출력한다. void close() 사용한 시스템 자원을 반납하고 출력 스트림을 얻는다. 4. 표준 스트림 (System.in, System.out, System.err)
자바에서 콘솔로부터 데이터를 입력받을 때 System.in을 사용하고 콘솔에 데이터를 출력할 때 System.out을 사용하고 콘솔에 에러를 출력할 때 System.err를 사용한다.
여기서 콘솔(Console)은 시스템을 사용하기 위해 키보드로 입력을 받고 화면으로 출력하는 소프트웨어를 말한다.
이클립스나 인텔리제이 같은 IDE에도 Console 뷰가 있어 키보드로 직접 입력을 받고 내용을 출력할 수 있다.
1) System.in
자바는 프로그램이 콘솔로부터 데이터를 입력받을 수 있도록 System 클래스의 in 정적 필드를 제공한다.
System.in은 InputStream 타입의 필드로 아래와 같이 InputStream 변수로 참조가 가능하다.
InputStream is = system.in;
키보드로부터 입력된 값을 확인하려면 InputStream의 read() 메소드로 한 바이트를 읽으면 된다.
리턴된 int 값에는 십진수 아스키 코드가 들어 있다.
int inputVal = is.read();
아스키 코드가 아닌 입력한 문자를 얻고 싶다면 char로 타입 변환하면 된다.
char inputChar = (char) is.read();
read()메소드는 1바이트만 읽기 때문에 한글과 같은 2바이트를 필요로 하는 유니코드는 read()메소드로 읽을 수 없다.
키보드로 입력된 한글을 얻으려면 read(byte[] b)나 read(byte[] b, int off, int len)메소드로 전체 입력된 내용을 바이트 배열로 받은 후에 이 배열을 이용해서 String 객체를 생성하면 된다.
2) System.out
System.out은 콘솔로 데이터를 출력하기 위해 필요한 필드로 System 클래스의 in 정적 필드를 제공한다.
out은 PrintStream 타입의 필드이다.
PrintStream은 OutputStream의 하위 클래스이여서 out 필드를 OutputStream 타입으로 변환해서 사용할 수 있다.
OutputStream os = Sytem.out;
콘솔에 1개의 바이트를 출력하려면 OutputStream의 write(int b) 메소드를 이용하면 된다.
이때 write메소드가 받는 매개값은 아스키 코드로 출력은 문자로 출력해준다.
즉, 아래의 코드를 실행하면 출력값은 'a'가 된다.
OutputStream os = System.out; byte b = 97; os.write(b); os.flush()
write(int b) 메소드는 1바이트만 보낼 수 있기 때문에 2바이트로 표현되는 한글은 출력할 수 없다.
한글을 출력하기 위해서는 한글을 바이트 배열로 얻은 다음에 read(byte[] b)나 read(byte[] b, int off, int len)메소드로 콘솔에 출력하면 된다.
String name = '홍길동'; byte[] nameBytes = name.getBytes(); os.write(nameBytes); os.flush();
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
[스터디 15주차] 람다식 (0) 2021.07.28 [스터디 14주차] 제네릭 (0) 2021.07.24 [스터디 12주차] 멀티쓰레드 프로그래밍 (0) 2021.07.14 [이클립스] javadoc 만들기 (0) 2021.07.11 [스터디 11주차] Enum (0) 2021.07.10