본문 바로가기

프로그래밍 언어/JAVA

JAVA 입문 - 보조 스트림

지난 글 : [프로그래밍 언어/JAVA] - JAVA 입문 - 문자 단위 스트림

 

보조 스트림이란

입출력 대상이 되는 파일이나 네트워크에 직접 쓰거나 읽는 기능은 없고 말 그대로 보조 기능을 추가하는 스트림이다. 이 보조 기능은 여러 스트림에 적용할 수 있다. 보조 스트림은 다른 스트림을 감사고 있다는 의미로 Wrapper 스트림이라고도 한다. 스스로는 입출력 기능이 없기에 생성자의 매개변수로 다른 스트림을 받게 되면 자신이 감싸고 있는 스트림이 읽거나 쓰는 기능을 수행할 때 보조 기능을 추가한다.

 

FilterInputStream과 FilterOutputStream

FilterInputStream과 FilterOutputStream은 보조 스트림의 상위 클래스다. 모든 보조 스트림은 FilterInputStream이나 FilterOutputStream을 상속받는다. 위에서 설명했듯 보조 스트림은 자료 입출력을 직접할 수 없기에 다른 기반 스트림을 포함한다. FilterInputStream과 FilterOutputStream의 생성자는 아래와 같다.

두 클래스 모두 다른 생성자는 제공하지 않는다. 따라서 이 클래스를 상속받은 보조 클래스도 상위 클래스에 디폴트 생성자가 없으므로 다른 스트림을 매개변수로 받아 상위 클래스를 호출해야 한다. FilterInputStream과 FilterOutputStream을 직접 생성하여 사용하는 경우는 거의 없다 하고 이를 상속한 하위 클래스를 프로그램에서 많이 사용한다고 한다. 보조 스트림을 배울 때 기억할 사항은 보조 스트림의 생성자에 항상 기반 스트림만 매개변수로 전달되는 것은 아니라 한다. 때로 또 다른 보조 스트림을 매개변수로 전달받을 수도 있다. 이때 전달되는 또 다른 보조 스트림은 내부적으로 기반 스트림을 포함하고 있다. 이런 경우 아래처럼 하나의 기반 스트림에 여러 보조 스트림 기능이 추가된다.

주로 사용하는 보조 스트림을 중심으로 살펴보자.

 

InputStreamReader와 OutputStreamWriter

지난 글에서 봤듯 바이트 단위로 자료를 읽으면 한글 같은 문자는 깨진다. 그래서 문자는 Reader나 Writer에서 상속받은 스트림을 사용해 자료를 읽거나 써야 한다. 하지만 바이트 자료만 입력되는 스트림이 있다. 예로 System.in 스트림이다. 또한 네트워크에서 소켓이나 인터넷이 연결되었을 때 읽거나 쓰는 스트림은 바이트 단위인 InputStream과 OutputStream이다. 이렇게 생성된 바이트 스트림을 문자로 변환해 주는 보조 스트림이 InputStreamReader와 OutputStreamWriter다. 보조 스트림은 입출력 기능이 없으므로 다른 입출력 스트림을 포함한다. InputStreamReader의 생성자는 아래와 같다.

InputStreamReader 생성자의 매개변수로 바이트 스트림과 문자 세트를 지정할 수 있다. 문자 세트란 문자를 표현하는 인코딩 방식이다. 바이트 자료가 문자로 변환될 때 지정된 문자 세트가 적용된다. 적용할 문자 세트를 명시하지 않으면 시스템이 기본으로 사용하는 문자 세트가 적용된다.

 

InputStreamReader의 모든 생성자는 InputStream(바이트 단위로 읽어 들이는 스트림)을 매개변수로 받는다. 생성자에서 매개변수로 받은 InputStream이 자료를 읽으면 InputStreamReader가 읽은 자료를 문자로 변환해 준다. InputStream인 FileInputStream을 사용해 InputStreamReader가 해주는 문자 변환 기능을 알아보자.

출력 결과

9행에 InputStreamReader(보조 스트림)가 FileInputStream(기반 스트림)을 매개변수로 받아 생성된다. FileInputStream은 바이트 단위로 자료를 읽기에 reader.txt에 쓰여 있는 '안녕하세요'를 읽을 수 없다. InputStreamReader는 파일 스트림이 바이트 단위로 읽어 들인 내용을 문자로 변환해 주는 역할을 한다. 실은 파일에서 문자를 읽는 경우 위처럼 InputStreamReader로 변환하지 않고 FileReader로 읽으면 된다. 위처럼 코딩을 한 이유는 InputStreamReader를 사용하기 위해서다. 

 

표준 입출력 스트림 System.in과 System.out은 모두 바이트 스트림이다. System.in은 콘솔 화면에서 한글을 읽으려면 InputStreamReader를 사용해야 한다. Scanner 클래스는 이런 변환이 필요 없어 콘솔 입력에 많이 쓰인다. 네트워크에서 쓰이는 클래스는 스트림을 생성하면 InputStream이나 OutputStream으로 생성된다고 한다. 예로 채팅 프로그램을 만든다 하면 바이트 단위로 사용하면 영어로만 채팅을 해야 한다. 이럴 때 읽어 들인 자료를 InputStreamReader나 OutputStreamWriter를 활용해 문자로 변환해 사용한다고 한다.

 

Buffered 스트림

Buffered 스트림은 내부적으로 8,192 바이트 크기의 배열을 갖고 있으며 이미 생성된 스트림에 배열 기능을 추가해 더 빠르게 입출력을 실행할 수 있는 버퍼링 기능을 제공한다. 이는 한 바이트나 한 문자 단위로 처리할 때보다 훨씬 빠르게 처리할 수 있다. 버퍼링 기능을 제공하는 스트림 클래스는 아래와 같다.

버퍼링 기능을 제공하는 스트림도 보조 스트림으로 다른 스트림을 포함하여 수행된다. BufferedInputStream의 생성자를 살펴보자.

BufferedInputStream은 보조 스트림이므로 생성자의 매개변수로 다른 InputStream을 가져야 한다. BufferedOutputStream은 OutputStream을, BufferedReader는 Reader를, BufferedWriter는 Writer 클래스를 생성자의 매개변수로 받는다. Buffered 스트림이 포함할 스트림이 입력 스트림인지 출력 스트림인지, 문자용인지 바이트용인지에 따라 그에 맞는 스트림을 사용한다.

 

Buffered 스트림을 사용할 때와 그렇지 않을 때의 수행 시간을 비교해본 책의 말을 빌리면 5MB 정도의 파일을 복사할 때 FileInputStream과 FileOutputStream을 사용해 한 바이트씩 읽어 새로운 파일에 작성하는 경우 파일 내용 전체를 복사하는 데 걸리는 시간이 232초라 한다. 하지만 보조 스트림 BufferedInputStream과 BufferedOutputStream을 사용해 파일을 복사한 경우 수행 시간이 0.079초로 속도가 굉장히 빠르다고 한다.

 

Buffered 스트림은 멤버 변수로 8,192 바이트 배열을 갖고 있다 보니, 한 번 자료를 읽을 때 8KB 정보를 한 번에 읽고 쓸 수 있어 1 바이트씩 읽고 쓸 때보다 훨씬 빠른 수행을 보장한다. 배열의 크기는 Buffered 스트림 생성자 매개변수로 지정할 수도 있다.

 

DataInputStream과 DataOutputStream

위에서 배운 스트림은 사람이 읽고 쓰는 텍스트 형식의 자료를 다룬다. 지금 배울 DataInputStream과 DataOutputStream은 메모리에 저장된 0, 1 상태를 그대로 읽거나 쓴다. 그래서 자료형의 크기가 그대로 보존된다. 두 스트림은 아래와 같은 생성자를 제공한다.

DataInputStream은 아래처럼 각 자료형별 메서드를 제공해 자료형에 따라 읽거나 쓸 때 사용할 수 있다.

DataOutputStream은 각 자료형 별로 read( )에 대응되는 write( ) 메서드를 제공한다.

자료형을 그대로 읽고 쓰는 스트림이기에 같은 정수라도 자료형에 따라 다르게 처리한다. writeByte(100)은 1 바이트로 쓰인 100을 의미하지만 writeInt(100)은 4 바이트로 쓰인 100을 의미한다. 따라서 자료를 쓸 때 사용한 메서드와 같은 자료형의 메서드로 읽어야한다. 정수 100을 쓰는 데 writeInt(100)을 쓰고 readByte( )로 읽으면 서로 사용한 메모리 크기가 달라 같은 값을 가져올 수 없다. 또 파일이든 네트워크든 자료를 쓸 때 사용한 메서드 순서대로 읽어야 한다. 

 

아래는 파일에 여러 자료형 값을 저장하는 코드다. 자료형을 유지해 저장하기 위해 DataInputStream과 DataOutputStream을 보조 스트림으로 사용했다.

출력 결과

파일 기반 스트림을 만들어 DataInputStream과 DataOutputStream 기능을 추가했다. 기반 스트림에서 쓸 수 없던 각 자료형의 자료를 그대로 읽고 쓸 수 있다. 파일에 쓴 것과 동일한 순서, 동일한 메서드로 읽어야 한다.

 

참고 서적 : 자바 프로그래밍 입문 - 박은종