입출력은 컴퓨터 내/외부의 장치와 프로그램간의 데이터를 주고 받는 것이다. 자바에서는 데이터를 주고 받기 위해 즉 입출력을 위해 스트림이라는 것을 쓴다. 이것은 람다와 스트림에서의 스트림과 이름이 같지만 다른 개념이다.
스트림은 연속적인 데이터의 흐름을 물에 비유해서 붙여진 이름이다. 흐르는 물은 하나의 방향으로만 흐른다. 이 점은 스트림의 단방향통신이라는 특성과 유사하다. 스트림은 입출력을 동시에 수행할 수 없다. 때문에 입력스트림(input stream)과 출력스트림(output stream), 총 2개의 스트림이 필요하다.
스트림은 먼저 보낸 데이터를 먼저 받는다. 또한 중간에 건너뜀 없이 연속적으로 데이터를 주고 받는다. 이 특성은 큐(Queue)의 FIFO(First In First Out) 구조와 비슷하다.
입력스트림 | 출력스트림 | 입출력 대상의 종류 |
---|---|---|
FileInputStream | FileOutputStream | 파일 |
ByteArrayInputStream | ByteArrayOutputStream | 메모리(byte배열) |
PipedInputStream | PipedOutputStream | 프로세스(프로세스간의 통신) |
AudioInputStream | AudioOutputStream | 오디오장치 |
자바의 스트림은 바이트단위로 데이터를 전송한다. 어떤 스트림을 쓸지는 어떤 대상에 대해 작업할 것인지에 따르기 때문에 위의 입력스트림과 출력스트림의 종류 중 적합한 것을 적용하면 된다. 이들은 모두 InputStream과 Outputstream의 자손들이다.
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) |
자바에서는 java.io
패키지에 입출력 관련 클래스들을 제공하고 있다. 위의 메서드들은 InputStream과 OutputStream에 정의된 읽기와 쓰기 수행 메서드들이다. read()와 write(int b) 메서드를 제외한 나머지는 일반 메서드라서 이것만 써도 되는 것은 아니다. 이 메서드들은 추상메서드인 read()와 write(int b)를 통해 구현된 것이기 때문에 이 메서드들이 우선적으로 구현되어 있지 않으면 소용이 없다.
보조 스트림이란 앞서 살펴본 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있는 스트림이다. 보완하는 용도이기 때문에 실제 데이터를 주고 받지 않고, 데이터를 입출력할 수 있는 기능은 없다. 따라서 스트림을 먼저 생성한 후에 보조 스트림을 생성해서 써야 한다.
입력스트림 | 출력스트림 | 설명 |
---|---|---|
FilterInputStream | FilterOutputStream | 필터를 이용한 입출력 처리 |
BufferedInputStream | BufferedOutputStream | 버퍼를 이용한 입출력 성능향상 |
DataInputStream | DataOutputStream | int, float와 같은 기본형 단위로 데이터를 처리하는 기능 |
SequenceInputStream | 없음 | 두 개의 스트림을 하나로 연결 |
LineNumberInputStream | 없음 | 읽어 온 데이터의 라인 번호를 카운트 |
ObjectInputStream | ObjectOutputStream | 데이터를 객체 단위로 읽고 쓰는데 사용. 주로 파일을 이용하며 객체 직렬화와 관련. |
없음 | PrintStream | 버퍼를 이용하며, 추가적인 print관련기능(print, print, println 메서드) |
PushbackInputStream | 없음 | 버퍼를 이용해서 읽어 온 데이터를 다시 되돌리는 기능(unread, push back to buffer) |
FileInputStream은 InputSream의 자손이고, BufferedInputStream, DataInputStream, LineNumberInputStream, PushbackInputStream은 모두 FileInputStream의 자손이기 때문에 입출력방법이 같다. 버퍼를 사용한 입출력은 사용하지 않았을 때보다 확연히 성능이 좋기 때문에 대부분의 경우에 버퍼를 이용한 보조스트림을 사용하는 것이 좋다.
위의 스트림은 모두 바이트 기반 스트림이었다. 바이트 기반이라 함은, 입출력 단위가 1 byte라는 의미이다. 하지만 Java에서는 char형이 2 byte기 때문에 바이트 기반의 스트림으로 문자를 처리할 때 어려움이 있다. 이를 보완하기 위해 있는 것이 문자 기반 스트림이다.
바이트기반 스트림 | 문자기반 스트림 |
---|---|
FileInputStream, FileOutputStream | FileReader, FileWriter |
ByteArrayInputStream, ByteArrayOutputStream | CharArrayReader, CharArrayWriter |
PipedInputStream, PipedOutputStream | PipedReader, PipedWriter |
StringReader, StringWriter |
문자기반 스트림은 바이트기반 스트림에서 InputStream/OutputStream 부분을 Reader/Writer로 바꾸면 된다. 마지막의 스트링버퍼I/O는 StringReader와 StringWriter로 완전히 대체되어 더 이상 사용되지 않는다.
BufferedReader와 BufferedWriter는 버퍼를 이용해 입출력의 효율을 높여준다. BufferedReader의 readLine()
을 사용하면 데이터를 라인 단위로 읽을 수 있다. BufferedWriter의 newLine()
을 사용하면 줄바꿈을 할 수 있다.
InputStreamReader와 OutputStreamWriter는 바이트 기반 스트림을 문자 기반 스트림으로 연결시켜준다. 바이트 기반 스트림의 데이터를 지정된 인코딩 문자 데이터로 변환하는 작업도 수행한다.
예를 들어 중국어로 작성된 파일을 읽을 때는 InputStreamReader로 인코딩이 중국어로 되어 있다는 것을 지정해주어야 파일이 깨지지 않고, 마찬가지로 파일에 텍스트 데이터를 저장할 때 OutputStreamWriter로 인코딩을 지정하지 않으면 OS가 자체적으로 사용하는 인코딩으로 데이터를 저장할 것이다.
import java.io.*;
public class ABSum {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
int num = Integer.parseInt(br.readLine());
while(num > 0) {
String line = br.readLine();
int pos = line.indexOf(" ");
int A = Integer.parseInt(line.substring(0, pos));
int B = Integer.parseInt(line.substring(pos+1));
int sum = A+B;
bw.write(Integer.toString(sum));
bw.newLine();
num--;
}
bw.close();
}
}
// 입력 :
//3
//1 2
//2 3
//4 5
// 출력 :
//3
//5
//9
BufferedReader와 BufferedWriter를 이용해 백준 15552번 문제를 푼 코드이다. BufferedReader로 첫째줄에 입력 받은 숫자가 0이 되기 전까지 입력 받은 숫자를 줄마다 반복해서 숫자로 뜯어낸 후, 그것을 더한 값을 BufferedWriter를 통해 줄마다 출력하게끔 작성하였다.
참고 자료
- 『자바의 정석 』 (저자 남궁성)
- 공식문서