본문 바로가기

java

보조 스트림

기본 스트림인 InputStream, OutputStream, Reader, Writer를 직접 사용해서 데이터를 입출력할 수 있지만, 데이터를 변환해서 입출력하거나, 데이터의 출력 형식을 지정하고 싶을 경우, 그리고 입출력 성능을 향상시키고 싶을 경우가 있을 것입니다. 이럴 때 기본 스트림에 보조 스트림을 연결하여 사용하면 편리하게 여러 기능들을 구현할 수 있습니다. 

 

보조 스트림을 연결하려면 보조 스트림을 생성할 때 자신이 연결될 스트림을 다음과 같이 생성자의 매개값으로 제공하면 됩니다. 

InputStream is = ...;
InputStreamReader reader = new InputStreamReader(is);

또한 보조 스트림의 생성자 매개값은 기본 스트림 이외에 또 다른 보조 스트림이 될 수 있습니다. 이 말은 보조 스트림을 연속적으로 연결할 수 있다는 것입니다. 

InputStream is = System.in;
InputStreamReader reader = new InputStreamReader(is);
BufferedReader br = new BufferedReader(reader);

 

 

1. 문자열 변환 보조 스트림

소스 스트림이 바이트 기반 스트림(InputStream, OutputStream, FileInputStream, FileOutputStream)이면서 입출력 데이터가 문자라면 Reader와 Writer로 변환해서 사용하는 것을 고려할 수 있습니다. 그 이유는 문자 입출력은 Reader와 Writer가 편리하기 때문입니다.

 

1-1. OutputStreamWriter

OutputStreamWriter은 바이트 기반 출력 스트림에 연결되어 문자 출력 스트림인 Writer로 변환하는 보조 스트림입니다. 예를 들어 파일 입출력을 위한 바이트 기반 FileOutputStream을 다음과 같이 Writer 타입으로 변환할 수 있습니다.

FileOutputStream fos = new FileOutputStream("...");
Writer writer = new OutputStreamWriter(fos);

 

1-2. InputStreamWriter

 InputStreamWriter는 바이트 기반 입력 스트림에 연결되어 문자 입력 스트림인 Reader로 변환하는 보조 스트림입니다. 

 

다음 예제는 파일로 문자를 저장하고 저장된 문자를 다시 읽습니다. 사용하는 소스 스트림은 바이트 기반입니다. 하지만 이들 스트림을 직접 사용하지 않고 각각 Writer, Reader로 변환하여 입출력을 합니다. 

2. 성능 향상 보조 스트림

프로그램 실행 성능은 입출력이 가장 늦은 장치를 따라갑니다. CPU가 아무리 좋아도 하드 디스크의 입출력이 늦어지면 프로그램 실행 성능은 하드 디스크의 처리 속도에 맞춰집니다. 네트워크로 데이터를 전송할 때도 마찬가지입니다. 느린 네트워크 환경이라면 컴퓨터 사양이 아무리 뛰어나도 메신저와 게임의 속도는 느릴 수밖에 없습니다. 

 

이 문제에 대해 완전한 해결책은 될 수 없지만, 프로그램이 입출력 소스와 직접 작업하지 않고 중간에 메모리 버퍼(buffer)와 작업함으로써 실행 성능을 향상시킬 수 있습니다. 예를 들어 프로그램은 직접 하드 디스크에 데이터를 보내지 않고 메모리 버퍼에 데이터를 보냄으로써 쓰기 속도가 향상됩니다. 버퍼는 데이터가 쌓이기를 기다렸다가 꽉 차게 되면 데이터를 한꺼번에 하드 디스크로 보냄으로써 출력 횟수를 줄여줍니다. 

 

기본적으로 출력 스트림은 내부에 작은 버퍼를 가지고 있습니다. 하지만 이것으로는 불충분합니다. 보조 스트림 중에서는 위와 같이 메모리 버퍼를 추가로 제공하여 프로그램의 실행 성능을 향상시키는 것들이 있습니다. 바이트 기반 스트림에서는 BufferedInputStream, BufferedOutputStream이 있고, 문자 기반 스트림에는 BufferedReader, BufferedWriter가 있습니다. 

 

BufferedOutputStream과 BufferedWriter는 프로그램에서 전송한 데이터를 내부 버퍼에 쌓아두었다가 버퍼가 꽉 차면 버퍼의 모든 데이터를 한꺼번에 보냅니다. 

 

BufferedInputStream과 BufferedReader는 입력 소스로부터 자신의 내부 버퍼 크기만큼 데이터를 미리 읽고 버퍼에 저장해둡니다. 프로그램을 외부의 입력 소스로부터 직접 읽는 대신 버퍼로부터 읽음으로써 읽기 성능이 향상됩니다. 

 

다음 예제는 성능 향상 보조 스트림을 사용했을 때와 사용하지 않았을 때의 파일 복사 실행 성능 차이를 보여줍니다. 실행 결과를 보면 보조 스트림을 사용했을 때 훨씬 성능이 좋아지는 것을 알 수 있습니다. 

BufferedReader는 라인 단위로 문자열을 읽는 매우 편리한 readLine() 메소드를 제공합니다. 정확히 말하면 readLine()은 엔터키(\r + \n) 이전의 모든 문자열을 읽고 리턴합니다. 이 메소드를 이용하면 키보드에서 입력한 내용을 라인 단위로 읽을 수 있습니다. 예를 들어 다음과 같은 텍스트 파일이 있습니다.

C 언어

Java 언어

Python 언어

 

라인 단위로 문자열을 얻고 싶다면 BufferedReader 보조 스트림을 생성하고 readLine()으로 반복해서 읽으면 됩니다. 총 3개의 라인이 있으므로 readLine()을 세 번 실행할 수 있는데, 더 이상 읽을 라인이 없다면 null을 리턴합니다. 이때 반복문을 빠져나오면 됩니다.

 

 

3. 기본 타입 입출력 보조 스트림

DataInputStream, DataOutputStream 보조 스트림을 연결하면 바이트 기반 스트림을 기본 타입으로 입출력할 수 있습니다.

 

다음은 기본 타입을 입출력하기 위해 DataInputStream, DataOutputStream이 제공하는 메소드들을 보여줍니다.

DataInputStream DataOutputStream 
boolean readBoolean() void writeBoolean(boolean v)
byte readByte() void writeByte(int v)
char readChar() void writeChar(int v)
double readDouble() void writeDouble(double v)
float readFloat() void writeFloat(float v)
int readInt() void writeInt(int v)
long readLong() void writeLong(long v)
short readShort() void writeShort(int v)
String readUTF() void writeUTF(String str)

주의할 점은 데이터 타입의 크기가 모두 다르므로 DataOutputStream으로 출력한 데이터를 다시 DataInputStream으로 읽어올 때는 출력한 순서와 동일한 순서로 읽어야 합니다. 예를 들어 출력할 때의 순서가 int -> boolean -> double이라면 읽을 때의 순서도 int -> boolean -> double이어야 합니다

 

기본 타입 입출력

결과:

홍길동: 95.5: 1

감자바: 90.3: 2

 

4. 프린터 보조 스트림

PrintStream과 PrintWriter는 프린터와 유사하게 출력하는 print(), println() 메소드를 가지고 있는 보조 스트림입니다. 지금까지 매우 비번히 사용했던 콘솔 출력 스트림인 System.out이 바로 PrintStream 타입이기 때문에 print(),  println() 메소드를 사용할 수 있었습니다. PrintStream은 바이트 기반 출력 스트림과 연결되고, PrintWriter는 문자 기반 출력 스트림과 연결됩니다. 

PrintStream ps = new PrintStream(바이트 기반 출력 스트림);
PrintWriter pw = new PrintWriter(문자 기반 출력 스트림);

라인 단위로 출력

 

결과:

* PrintStream과 BufferedReader 

 PrintStream의 println() 메소드로 출력한 내용은 BufferedReader의 readLine()으로 읽으면 매우 편리합니다. println()은 개행 문자를 포함하고 출력함으로 readLine() 메소드가 개행 문자 앞까지 쉽게 읽을 수 있기 때문입니다.

 

 

5. 객체 입출력 보조 스트림

ObjectOutputStream과 ObjectInputStream 보조 스트림을 연결하면 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수 있습니다. 

 

ObjectOutputStream은 객체를 직렬화하는 역할을 하고, ObjectInputStream은 객체로 역직렬화하는 역할을 합니다. 직렬화란 객체를 바이트 배열로 만드는 것이고, 역직렬화란 바이트 배열을 다시 객체로 복원하는 것입니다. 

ObjectInputStream ois = new ObjectInputStream(바이트 기반 입력 스트림);
ObjectOutputStream oos = new ObjectOutputStrea(바이트 기반 출력 스트림);

ObjectOutputStream의 writeObject() 메소드는 객체를 직렬화해서 출력 스트림으로 보냅니다. 

oos.writeObject(객체);

반대로  ObjectInputStream의 readObject() 메소드는 입력 스트림에서 읽은 바이트를 역직렬화해서 객체로 다시 복원해서 리턴합니다. 리턴 타입은  Object 타입이기 때문에 원래 타입으로 다음과 같이 강제 변환해야 합니다.

객체타입 변수 = (객체 타입) ois.readObject();

**자바는 모든 객체를 직렬화하지 않습니다. java.io.Seriallizable 인터페이스를 구현한 객체만 직렬화합니다. Serializable 인터페이스는 메소드 선언이 없는 인터페이스입니다. 객체를 파일로 저장하거나, 네트워크로 전송할 목적이라면 개발자는 클래스 선언 시 implements Serializable을 추가해야 합니다. 이것을 개발자가 JVM에게 직렬화해도 좋다고 승인하는 역할을 합니다. 

 

다음 예제는 List 컬렉션을 파일로 저장하고 있습니다. List 컬렉션의 구현 클래스로 ArrayList를 사용하는데, ArrayList는 Serializable 인터페이스를 구현하고 있습니다. 따라서 파일에 저장이 가능하다는 뜻입니다. 주의할 점은  ArrayList에 저장되는 객체 역시 Serializable인터페이스를 구현하고 있어야 한다는 것입니다.그래서 Board역시 Serializable 인터페이스를 구현하고 있습니다.

 

결과:

1 제목1 내용1 글쓴이1 2021-02-01

2 제목2 내용2 글쓴이2 2021-02-01

3 제목3 내용3 글쓴이3 2021-02-01

4 제목4 내용4 글쓴이4 2021-02-01

 

 

결과:

1: package sec06.exam06;

2: 

3: import java.io.*;

4: 

5: 

6: public class Hello {

7: public static void main(String[] args) throws Exception {

8: String filePath = "src/sec06/exam06/Hello.java";

9: 

10: FileReader fr = new FileReader(filePath);

11: BufferedReader br = new BufferedReader(fr);

12: 

13: int i = 0;

14: String rowData;

15: while((rowData = br.readLine()) != null) {

16: System.out.println(++i + ": " + rowData);

17: 

18: }

19: 

20: }

21: }

'java' 카테고리의 다른 글

입출력 관련 API  (0) 2021.02.01
java 입출력 스트림  (0) 2021.01.30
java 스택, 큐  (0) 2021.01.30
java 컬렉션 프레임워크  (0) 2021.01.29
스레드 제어  (0) 2021.01.29