자바의 입출력
I/O란 Input과 Output의 약자로 입력과 출력, 간단히 줄여서 입출력이라고 합니다. 입출력은 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고 받는 것을 말합니다. 예를 들면 키보드로부터 데이터를 입력받는다듣가 System.out.println()을 이용해서 화면에 출력하는 것이 가장 기본적인 입출력의 예입니다.
스트림(stream)
자바에서 입출력을 수행하려면, 즉 어느 한쪽에서 다른 쪽으로 데이터를 전달하려면, 두 대상을 연결하고 데이터를 전송할 수 있는 무언가가 필요한데 이것을 스트림(stream)이라고 정의했습니다. 스트림은 우리가 입출력을 쉽게 처리할 수 있게 도와줍니다.
스트림이란 데이터를 운반하는데 사용되는 연결통로
스트림은 연속적인 데이터의 흐름을 물에 비유해서 붙여진 이름인데, 여러 가지로 유사한 점이 많습니다. 물이 한쪽 방향으로만 흐르는 것과 같이 스트림은 단방향 통신만 가능하기 때문에 하나의 스트림으로 입력과 출력을 동시에 처리할 수 없습니다.
그래서 입력과 출력을 동시에 수행하려면 입력을 위한 입력스트림(input stream)과 출력을 위한 출력스트림(output stream), 모두 2개의 스트림이 필요합니다.

스트림은 먼저 보낸 데이터를 먼저 받게 되어 있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고 받습니다. 큐(queue)와 같은 FIFO(First In First Out)로 동작한다고 생각하면 됩니다.
바이트 기반 스트림 - InputStream, OutputStream
스트림은 바이트 단위로 데이터를 전송하며 입출력 대상에 따라 다음과 같은 입출력 스트림이 있습니다.
| 입력스트림 | 출력스트림 | 입출력 대상의 종류 |
| FileInputStream | FileOutputStream | 파일 |
| ByteArrayInputStream | ByteArrayOutputStream | 메모리(byte배열) |
| PipedInputStream | PipedOutputStream | 쓰레드(쓰레드 간 통신) |
| AudioInputStream | AudioOutputStream | 오디오장치 |
위와 같이 여러 종류의 입출력 스트림이 있으며, 어떠한 대상에 대해서 작업을 할 것인지 그리고 입력을 할 것인지 출력을 할 것인지에 따라서 해당 스트림을 선택해서 사용하면 됩니다. 이들은 모두 InputStream 또는 OutputStream의 자손들이며, 각각 읽고 쓰는데 필요한 추상 메서드를 자신에 맞게 구현해 놓았습니다.
자바에서는 java.io 패키지를 통해서 많은 종류의 입출력 관련 클래스를 제공하고 있으며, 입출력을 처리할 수 있는 표준화된 방법을 제공함으로써 입출력의 대상이 달라져도 동일한 방법으로 입출력이 가능하기 때문에 프로그래밍을 하기에 편리합니다.
| 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) |
read()의 반환타입이 byte가 아니라 int인 이유는 read()의 반환값의 범위가 0~255와 -1이기 때문입니다.
InputStream의 read()와 OutputStream의 write(int b)는 입출력의 대상에 따라 읽고 쓰는 방법이 다를 것이기 때문에 각 상황에 알맞게 구현하라는 의미에서 추상메서드로 정의되어 있습니다.
read()와 write(int b)를 제외한 나머지 메서드들은 추상 메서드가 아니지만, 내부적으로 추상 메서드인 read()와 write(int b)를 이용해서 구현한 것들이기 때문에 이 두 메서드가 구현되어 있지 않으면 아무런 의미가 없습니다. 이렇게 추상 메서드를 호출해도 실제로는 추상 클래스를 상속받아서 추상 메서드를 구현한 클래스의 인스턴스에 대해서 추상 메서드가 호출될 것이기 때문에 추상 메서드를 호출하는 코드를 작성해도 아무런 문제가 되지 않습니다.
Truncation
Truncation이란 값을 저장하거나 변환할 때, 저장 공간이 부족하거나 형이 맞지 않아 상위 비트 또는 일부 정보가 잘려 나가는 현상을 의미합니다. 바이트 기반 스트림은 1byte 단위로 입출력을 하는데, OutputStream의 write(int b) 추상 메서드의 매개변수 타입을 보면 인자로 int 범위를 받습니다. 이때 byte보다 큰 범위의 int 값을 인자로 받아서 어떻게 처리하는지 확인해보겠습니다.
다음은 OutputStream의 구현체인 ByteArrayOutputStream에서 구현한 write(int b) 메서드입니다.
@Override
public synchronized void write(int b) {
ensureCapacity(count + 1);
buf[count] = (byte) b;
count += 1;
}
코드에서 매개변수로 받은 int b 를 byte로 강제 형변환을 한뒤 저장하는 것을 알 수 있습니다. 이 과정에서 상위 24비트(3byte)는 잘려나가게 됩니다. 이는 OutputStream의 다른 구현체들도 마찬가지입니다.
따라서 다음과 같은 결과를 가지게 됩니다.

OutputStream의 write(int b)로 정수를 기록하면 강제 형변환에 의해 마지막 8비트(1byte)만 기록이 됩니다. 이 값을 문자(char)로 변경하면 정수 1의 경우 U+0001(SOH)로 출력되고, 정수 2025의 경우 U+00E9(é)로 출력됩니다.
어떤 문자를 제대로 표시할 수 없을 때 대신 출력하는 대체 문자를 'Fallback 문자'라고 하며, 유니코드 U+00E9(é)의 경우 콘솔에서 지원하지 못하는 경우 fallback 문자로 표시됩니다.
보조 스트림
앞에서 언급한 스트림 외에도 스트림의 기능을 보완하기 위한 보조 스트림이 제공됩니다. 보조 스트림은 실제 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력할 수 있는 기능은 없지만, 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있습니다. 그래서 입출력을 위해서는 스트림을 먼저 생성한 다음에 이를 이용해서 보조 스트림을 생성해야 합니다.
예를 들어 파일을 읽기 위해 FileInputStream을 사용할 때, 입력 성능을 향상시키이 위해서 버퍼를 사용하는 보조 스트림인 BufferedInputStream을 사용하는 코드는 다음과 같습니다.
// 기반 스트림 생성
FileInputStream fis = new FileInputStream("test.txt");
// 기반 스트림을 이용해서 보조 스트림 생성
BufferedInputStream bis = new BufferedInputStream(fis);
// 보조 스트림으로부터 데이터 읽기
bis.read();
코드 상으로는 보조 스트림인 BufferedInputStream이 입력기능을 수행하는 것처럼 보이지만, 실제 입력기능은 BufferedInputStream과 연결된 FileInputStream이 수행하고, 보조 스트림인 BufferedInputStream은 버퍼만 제공합니다. 버퍼를 사용한 입출력과 사용하지 않은 입출력의 성능 차이는 상당하기 때문에 대부분의 경우 버퍼를 이용한 보조 스트림을 사용합니다.
| 입력 | 출력 | 설명 |
| FilterInputStream | FilterOutputStream | 필터를 이용한 입출력 처리 |
| BufferedInputStream | BufferedInputStream | 버퍼를 이용한 입출력 성능 향상 |
| DataInputStream | DataOutputStream | int, float와 같은 기본형 단위로 데이터를 처리하는 기능 |
| SequenceInputStream | 없음 | 두 개의 스트림을 하나로 연결 |
| LineNumberInputStream | 없음 | 읽어 온 데이터의 라인 번호를 카운트 (JDK 1.1부터 LineNumberReader로 대체) |
| ObjectInputStream | ObjectOutputStream | 데이터를 객체단위로 읽고 쓰는데 사용 (주로 파일을 이용하며 객체 직렬화와 관련) |
| 없음 | PrintStream | 버퍼를 이용하며, 추가적인 print 관련 기능 (print, printf, println) |
| PushbackInputStream | 없음 | 버퍼를 이용해서 읽어 온 데이터를 다시 되돌리는 기능 (unread, push back to buffer) |
이러한 보조 스트림은 모두 FilterInputStream의 자손들이고, FilterInputStream은 InputStream의 자손이라서 결국 모든 보조 스트림들은 InputStream과 OutputStream의 자손들이므로 입출력방법 또한 같습니다.
문자 기반 스트림 - Reader, Writer
앞에서 알아본 스트림은 모두 바이트 기반의 스트림이었습니다. 바이트 기반이라 함은 입출력 단위가 1byte라는 뜻입니다. C언어와 달리 Java에서는 한 문자를 의미하는 char형이 1byte가 아니라 2byte이기 때문에 바이트 기반의 스트림으로 2byte인 문자를 처리하는 데는 어려움이 있습니다.
이 점을 보완하기 위해서 문자 기반의 스트림이 제공됩니다. 문자데이터를 입출력할 때는 바이트기반 스트림 대신 문자 기반 스트림을 사용합니다.
| 바이트 기반 스트림 | 문자 기반 스트림 |
| FileInputStream FileOutputStream |
FileReader FileWriter |
| ByteArrayInputStream ByteArrayOutputStream |
CharArrayReader CharArrayWriter |
| PipedInputStream PipedOutputStream |
PipedReader PipedWriter |
| StringReader StringWriter |
문자 기반 스트림의 이름은 바이트 기반 스트림으 이름에서 InputStream -> Reader 로 OutputStream -> Writer 로 바꾸면 됩니다. 단, ByteArrayInputStream에 대응하는 문자 기반 스트림은 char배열을 사용하는 CharArrayReader입니다.
그리고 보조 스트림 역시 문자 기반 보조 스트림이 존재하며 사용 목적과 방식은 바이트 기반 보조 스트림과 다르지 않습니다.
| InputStream | Reader |
| abstract int read() int read(byte[] b) int read(byte[] b, int off, int len) |
int read() int read(char[] cbuf) abstract int read(char[] cbuf, int off, int len) |
| OutputStream | Writer |
| abstract void write(int b) void write(byte[] b) void write(byte[] b, int off, int len) |
void write(int c) void write(char[] cbuf) abstract void write(char[] cbuf, int off, int len) void write(String str) void write(String str, int off, int len) |
위 표는 바이트 기반 스트림과 문자 기반 스트림의 읽기와 쓰기에 사용되는 메서드를 비교한 것인데 byte배열 대신 char배열을 사용한다는 것과 추상 메서드가 달라졌습니다. 바이트 기반 스트림에서는 1byte씩 읽는 메서드가 추상 메서드로 되어 있었지만, 문자 기반 스트림에서는 1문자씩 읽는 메서드가 아닌, 여러 문자를 한 번에 읽는 메서드가 추상 메서드로 되어있습니다. 이는 문자 기반 스트림은 문자 버퍼링 중심의 고수준 클래스이기 때문에 최소 단위가 아닌 효율적 단위를 기본으로 한 것입니다.
바이트 기반 스트림
InputStream과 OutputStream
앞서 얘기한 바와 같이 InputStream과 OutputStream은 모든 바이트 기반의 스트림의 조상이며 다음과 같은 메서드들이 선언되어 있습니다.
| InputStream 메서드 | 설 명 |
| abstract int read() | 추상 메서드. 1바이트 읽기 |
| int read(byte[] b) | 배열 전체 읽기 |
| int read(byte[] b, int off, int len) | 일부 구간 읽기 |
| long skip(long n) | n바이트 건너뛰기 |
| int available() | 읽을 수 있는 바이트 수 반환 |
| void close() | 스트림 닫기 |
| void mark(int readlimit) | 현재 위치 마크 설정 |
| void reset() | 마크된 위치로 복귀 |
| boolean markSupported() | mark/reset 지원 여부 확인 |
| byte[] readAllBytes() | 전체 데이터를 배열로 읽기 (Java 9+) |
| byte[] readNBytes(int len) | 최대 len바이트 읽기 (Java 9+) |
| int readNBytes(byte[] b, int off, int len) | 최대 len바이트 읽어 저장 (Java 11+) |
| long transferTo(OutputStream out) | 다른 출력 스트림으로 복사 (Java 9+) |
| void skipNBytes(long n) | 정확히 n바이트 건너뛰기 (Java 12+) |
| static InputStream nullInputStream() | EOF만 반환하는 빈 스트림 생성 (Java 11+) |
| OutputStream 메서드 | 설 명 |
| abstract void write(int b) | 추상 메서드. 1바이트를 출력 (0~255) |
| void write(byte[] b) | 배열 전체를 출력 (write(b, 0, b.length) 호출) |
| void write(byte[] b, int off, int len) | 배열의 일부 구간 출력 |
| void flush() | 버퍼에 남은 데이터를 강제로 출력 |
| void close() | 스트림을 닫고 리소스 해제 |
| static OutputStream nullOutputStream() | 아무것도 출력하지 않는 “빈 스트림” 반환 (Java 11+) |
스트림의 종류에 따라서 mark()와 reset()을 사용하여 이미 읽은 데이터를 되돌려서 다시 읽을 수 있습니다. 이 기능을 지원하는 스트림인지 확인하는 markSupported()를 통해서 알 수 있습니다.
JDK 12에서 skipNBytes(long n), JDK 9에서 readNBytes(int len) 등의 메서드가 추가되었는데, 기존의 메서드와 달리 지정된 만큼의 byte를 정확히 건너뛰거나 읽습니다. 기존의 skip(n)이나 read(bArr, off, len)는 호출 당시에 상황에 따라 지정한 것보다 적은 수를 건너뛰거나 읽어왔습니다. 예를 들어 read(bArr, 0 ,5)를 호출했는데 읽을 수 있는 데이터가 3byte뿐이면 3byte만 읽어서 반환하지만, readNBytes(5)는 5byte를 모두 읽을 때까지 기다립니다.
JDK 11부터 추가된 nullInputStream()은 빈 입력 스트림을 반환하는데, 빈 입력 스트림은 입력 스트림이 필요한데 적당한게 없을 때 사용합니다. null보다 빈 문자열을 사용하는 것과 같은 개념입니다.
마찬가지로 nullOutputStream()은 널 출력 스트림을 반환하는데, 이 스트림은 일반 스트림과 똑같이 동작합니다. 다만, 출력한 내용이 출력되지 않을 뿐입니다. 예를 들어 파일에 로그를 출력하는 프로그램에 널 출력 스트림을 지정하면 일시적으로 로그가 출력되지 않게 할 수 있습니다.
flush()는 버퍼가 있는 출력스트림의 경우에만 의미가 있으며, OutputStream에 정의된 flush()는 아무런 일도 하지 않습니다.
프로그램이 종료될 때, JVM이 사용하던 모든 자원을 반환하므로 사용하고 닫지 않은 스트림도 반환되지만, 가능하면 스트림을 사용해서 모든 작업을 마치고 난 후에는 close()를 호출해서 반드시 닫아 주는 습관을 들이는 것이 좋습니다.
ByteArrayInputStream과 같이 메모리를 사용하는 스트림과 System.in, System.out과 같은 표준 입출력 스트림은 닫지 않아도 됩니다.
ByteArrayInputStream과 ByteArrayOutputStream
ByteArrayInputStream과 ByteArrayOutputStream은 메모리, 즉 바이트배열에 데이터를 입출력 하는데 사용되는 스트림입니다. 자주 사용되지는 않지만 주로 다른 곳에 입출력하기 전에 데이터를 임시로 바이트배열에 담아서 변환 등의 작업을 하는데 사용됩니다.
다음은 스트림을 이용해서 읽고 쓰는 예제입니다.
public static void main(String[] args) {
byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
byte[] outSrc = null;
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
int data = 0;
while ((data = input.read()) != -1) {
output.write(data);
}
outSrc = output.toByteArray();
System.out.println(Arrays.toString(outSrc));
}
ByteArrayInputStream/ByteArrayOutputStream을 이용해서 바이트 배열 inSrc의 데이터를 outSrc로 복사하는 예제인데, read()와 write()를 사용하는 가장 기본적인 방법을 보여줍니다.
바이트 배열은 사용하는 자원이 메모리 밖에 없으므로 가비지 컬렉터에 의해 자동적으로 자원을 반환하므로 close()를 이용해서 스트림을 닫지 않아도 됩니다.
다음 예제는 배열을 사용해서 입출력 작업이 보다 효율적으로 이루어지도록 했습니다.
public static void main(String[] args) {
byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
byte[] outSrc = null;
byte[] temp = new byte[10];
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
input.read(temp, 0, temp.length);
output.write(temp, 5, 5);
outSrc = output.toByteArray();
System.out.println(Arrays.toString(outSrc));
}
read(bArr, off, len)와 write(bArr, off, len)을 사용하여 입출력하는 방법을 보여주는 예제입니다. 이전 예제와 달리 byte[] 을 사용하여 한 번에 배열의 크기만큼 읽고 쓸 수 있습니다. temp배열의 크기인 10byte 만큼 읽어왔지만 output에 출력할 때는 temp[5]부터 5byte만 출력하였습니다.
배열을 이용하면 입출력의 효율이 증가하므로 가능하면 입출력 대상에 따라 적절한 크기의 배열을 사용하는 것이 좋습니다.
다음 예제는 배열의 크기보다 입력 소스의 크기가 클 때의 예제입니다.
public static void main(String[] args) {
byte[] inSrc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
byte[] outSrc = null;
byte[] temp = new byte[4];
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
try {
while (input.available() > 0) {
input.read(temp);
output.write(temp);
System.out.println("temp: " + Arrays.toString(temp));
outSrc = output.toByteArray();
System.out.println("outSrc: " + Arrays.toString(outSrc));
}
} catch (IOException e) {}
}

read()나 write()이 IOException을 발생시킬 수 있기 때문에 try-catch문으로 감싸주었습니다. available()은 블로킹없이 읽어 올 수 있는 바이트의 수를 반환합니다.
하지만 결과가 예상과 다르게 나왔는데, 그 이유는 마지막에 읽은 배열의 9번째와 10번째 요소 값인 8, 9만 출력해야하는데 temp에 남아있던 6, 7까지 출력했기 때문입니다. 보나 나은 성능을 위해서 temp에 담긴 내용을 지우고 쓰는 것이 아니라 그냥 기존의 내용 위에 덮어쓰다보니 생긴 문제입니다.
원하는 결과를 얻기 위해서는 아래 오른쪽 코드와 같이 수정해야 합니다. 왼쪽의 코드는 배열의 내용 전체를 출력하지만, 오른쪽의 코드는 읽어 온 만큼(len)만 출력합니다.

FileInputStream과 FileOutputStream
FIleInputStream/FileOutputStream 은 파일에 입출력을 하기 위한 스트림입니다. 실제 프로그래밍에서 많이 사용되는 스트림 중 하나입니다.
| FileInputStream 생성자 | 설 명 |
| FileInputStream(String name) | 파일 경로(문자열)로 입력 스트림 생성 |
| FileInputStream(File file) | File 객체로 입력 스트림 생성 |
| FileInputStream(FileDescriptor fdObj) | 파일 디스크립터로 입력 스트림 생성 (저수준 I/O용) |
FileDescriptor는 OS가 관리하는 열린 파일의 정보가 담긴 행의 번호를 저장합니다.
| FileOutputStream 생성자 | 설 명 |
| FileOutputStream(String name) | 파일 경로(문자열)로 출력 스트림 생성 (기존 내용 덮어씀) |
| FileOutputStream(String name, boolean append) | 파일 경로 + append 여부 지정 (true면 이어쓰기) |
| FileOutputStream(File file) | File 객체로 출력 스트림 생성 (덮어쓰기) |
| FileOutputStream(File file, boolean append) | File 객체 + 이어쓰기 여부 지정 |
| FileOutputStream(FileDescriptor fdObj) | 파일 디스크립터로 출력 스트림 생성 (저수준 I/O용) |
다음은 파일 스트림을 이용해서 Main.java 파일의 내용을 그대로 Main.bak로 복사하는 예제입니다.
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("src/Main.java");
FileOutputStream fos = new FileOutputStream("src/Main.bak");
int data = 0;
while ((data = fis.read()) != -1) {
fos.write(data);
}
fis.close();
fos.close();
} catch (IOException e) { }
}

바이너리 파일이 아닌 텍스트 파일을 다루는 경우에는 문자 기반의 스트림인 FileReader/FileWriter를 사용하는 것이 더 좋습니다.
JDK 9부터는 InputStream에 추가된 transferTo()를 이용하면 위 예제의 while문을 한 줄로 대체할 수 있습니다.

바이트 기반의 보조 스트림
FilterInputStream과 FilterOutputStream
FilterInputStream/FilterOutputStream은 InputStream/OutputStream의 자손이면서 모든 보조 스트림의 조상입니다. 보조 스트림은 자체적으로 입출력을 할 수 없기 때문에 기반 스트림을 필요로 합니다. 다음은 FilterInputStream/FilterOutputStream의 생성자입니다.
protected FilterInputStream(InputStream in)
public FilterOutputStream(OutputStream out)
FilterInputStream/FilterOutputStream의 모든 메서드는 단순히 기반 스트림의 메서드를 그대로 호출할 뿐입니다. 이는 FilterInputStream/FilterOutputStream 자체로는 아무런 일도 하지 않음을 의미합니다. FilterInputStream/FilterOutputStream은 상속을 통해 원하는 작업을 수행하도록 읽고 쓰는 메서드를 오버라이딩해야 합니다.
public class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
@Override
public int read() throws IOException {
return in.read();
}
...
}
생성자 FilterInputStream(InputStream in)는 접근 제어자가 protected이기 때문에 FilterInputStream의 인스턴스를 생성해서 사용할 수 없고 상속을 통해서 오버라이딩되어야 합니다. FilterInputStream/FilterOutputStream을 상속받아서 기반 스트림에 보조 기능을 추가한 보조 스트림 클래스는 다음과 같습니다.
- FilterInputStream의 자손: BufferedInputStream, DataInputStream, PushbackInputStream 등
- FilterOutputStream의 자손: BufferedOutputStream, DataOutputStream, PrintStream 등
FilterOutputStream과 달리 FilterInputStream는 단순히 입력 스트림을 감싸서 그대로 전달하는 것만으로는 실질적인 의미가 없기 때문에, 직접 인스턴스를 생성하지 못하도록 생성자가 protected로 제한되어 있습니다.
BufferedInputStream과 BufferedOutputStream
BufferedInputStream과 BufferedOuputStream은 스트림의 입출력 효율을 높이기 위해 버퍼를 사용하는 보조 스트림입니다. 한 바이트씩 입출력하는 것보다는 버퍼(바이트 배열)를 이용해서 한 번에 여러 바이트를 입출력하는 것이 빠르기 때문입니다.
| BufferedInputStream 생성자 | 설 명 |
| BufferedInputStream(InputStream in) | 입력 스트림 in을 버퍼링하여 효율적으로 읽기 위한 보조 스트림 생성. 기본 버퍼 크기(8KB = 8192 byte) 사용. |
| BufferedInputStream(InputStream in, int size) | 버퍼 크기를 직접 지정하여 입력 스트림을 감쌈. size는 버퍼의 바이트 단위 크기. |
BufferedInputStream의 버퍼크기는 입력소스로부터 한 번에 가져올 수 있는 데이터의 크기로 지정하면 좋습니다. 보통 입력 소스가 파일인 경우 8KB(8192byte) 정도의 크기로 하는 것이 보통이며, 버퍼의 크기를 변경해가면서 테스트하면 최적의 버퍼 크기를 알아낼 수 있습니다.
프로그램에서 입력 소스로부터 데이터를 읽기 위해 처음으로 read메서드를 호출하면, BufferedInputStream은 입력 소스로부터 버퍼 크기만큼 데이터를 읽어다 자신의 내부 버퍼에 저장합니다. 이제 프로그램에서는 BufferedInputStream의 버퍼에 저장된 데이터를 읽으면 됩니다. 외부 입력 소스로부터 읽는 것보다 내부의 버퍼로부터 읽는 것이 훨씬 빠르기 때문에 그만큼 작업 효율이 높아집니다.
프로그램에서 버퍼에 저장된 모든 데이터를 다 읽고 그 다음 데이터를 읽기위해 read메서드가 호출되면, BufferedInputStream은 입력 소스로부터 다시 버퍼 크기 만큼의 데이터를 읽어다 버퍼에 저장해 놓습니다.
| BufferedOutputStream 생성자 / 메서드 | 설 명 |
| BufferedOutputStream(OutputStream out) | 출력 스트림 out을 버퍼링하여 효율적으로 쓰기 위한 보조 스트림 생성. 기본 버퍼 크기(8KB) 사용. |
| BufferedOutputStream(OutputStream out, int size) | 버퍼 크기를 직접 지정하여 출력 스트림을 감쌈. size는 버퍼의 바이트 단위 크기. |
| flush() | 버퍼의 모든 내용을 출력 소스에 출력한 다음, 버퍼를 비움 |
| close() | flush()를 호출해서 버퍼의 모든 내용을 출력소스에 출력하고, BufferedOutputStream 인스턴스가 사용하던 모든 자원을 반환 |
BufferedOutputStream 역시 버퍼를 이용해서 출력 소스와 작업합니다. 입력 소스에서 데이터를 읽을 때와 반대로, 프로그램에서 write메서드를 이용한 출력이 BufferedOutputStream의 버퍼에 저장됩니다.
버퍼가 가득 차면, 그 때 버퍼의 모든 내용을 출력 소스에 출력합니다. 그리고는 버퍼를 비우고 다시 프로그램으로부터의 출력을 저장할 준비가 됩니다. 버퍼가 가득 찼을 때만 출력 소스에 출력을 하기 때문에, 마지막 출력 부분이 출력 소스에 쓰이지 못하고 BufferedOutputStream의 버퍼에 남아있는 채로 프로그램이 종료될 수 있다는 점을 주의해야 합니다. 그래서 프로그램에서 모든 출력작업을 마친 후 BufferedOutputStream에 close()나 flush()를 호출해서 마지막에 버퍼에 있는 모든 내용이 출력 소스에 출력되게 해야 합니다.
다음은 크기가 5인 BufferedOutputStream을 이용하여 1~9까지 출력하는 예제입니다.
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream("src/123.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos, 5);
for (int i = '1'; i <= '9'; i++) {
bos.write(i);
}
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
그러나 실제로 위 코드를 실행해보면 5까지만 출력된 것을 알 수 있습니다. 그 이유는 버퍼에 남아있는 데이터가 출력되지 못한 상태로 프로그램이 종료되었기 때문입니다.

이 예제에서 fos.close()를 호출해서 스트림을 닫아주기는 했지만, 이래서는 BufferedOutputStream의 버퍼에 있는 내용이 출력되지 않습니다. bos.close()를 호출해서 닫아주어야 버퍼에 남아있던 모든 내용이 출력됩니다.
public class FilterOutputStream extends OutputStream {
protected OutputStream out;
public FilterOutputStream(OutputStream out) {
this.out = out;
}
...
@Override
public void close() throws IOException {
try {
flush();
} catch (Throwable e) {
flushException = e;
throw e;
} finally {
out.close();
}
}
}
BufferedOutputStream에서 close()를 호출하면 FilterOutputStream의 close()가 호출됩니다. 위 코드를 보면 FilterOutputStream에서는 close()시에 기반 스트림의 close()를 호출해주는 것을 알 수 있습니다. 이처럼 보조 스트림을 사용한 경우에는 기반 스트림을 명시적으로 close()나 flush()를 호출할 필요없이, 보조 스트림의 close()를 호출해주면 됩니다.
DataInputStream과 DataOutputStream
DataInputStream/DataOutputStream도 각각 FilterInputStream/FilterOutputStream의 자손이며 DataInputStream은 DataInput인터페이스를, DataOutputStream은 DataOutput인터페이스를 구현하였기 때문에, 데이터를 읽고 쓰는데 있어서 1 byte씩이 아닌, 기본형 단위로 읽고 쓸 수 있다는 장점이 있습니다.
DataOutputStream은 해당 자료형의 크기만큼의 ‘바이너리(바이트 단위)’로 저장합니다. 예를 들어 writeInt()는 int 값을 4바이트의 이진 데이터로 출력합니다. 또한 DataInputStream으로 다시 읽을 때는 기록한 순서와 자료형의 크기를 정확히 지켜야 올바르게 역직렬화할 수 있습니다.
| DataInputStream 생성자 / 메서드 | 설 명 |
| DataInputStream(InputStream in) | 입력 스트림을 감싸서, 기본 자료형 단위(int, long, double, 등)로 읽을 수 있게 하는 보조 스트림 생성 |
| boolean readBoolean() byte readByte() char readChar() double readDouble() float readFloat() int readInt() long readLong() short readShort() int readUnsignedByte() int readUnsignedShort() |
각 타입에 맞게 값을 읽기 (더 이상 읽을 값이 없으면 EOFException 발생) |
| String readUTF() |
UTF 형식으로 인코딩된 문자열 읽기 |
| void readFully(byte[] b) void readFully(byte[] b, int off, int len) |
지정된 배열 또는 범위만큼 읽기 |
| int skipBytes(int n) | n바이트를 건너뜀 |
| DataOutputStream 생성자 / 메서드 | 설 명 |
| DataOutputStream(OutputStream out) | 출력 스트림을 감싸서, 기본 자료형 단위(int, long, double, 등)로 쓸 수 있게 하는 보조 스트림 생성 |
| void writeBoolean(boolean v) void writeByte(int v) void writeChar(int v) void writeDouble(double v) void writeFloat(float v) void writeInt(int v) void writeLong(long v) void writeShort(int v) |
각 자료형에 알맞은 값들을 출력 |
| void writeUTF(String str) | UTF 형식으로 문자열 쓰기 |
| int size() | 지금까지 기록된 바이트 수 반환 |
다음은 ByteArrayOutputStream을 기반으로 하는 DataOutputStream을 생성한 후, DataOutputStream의 메서드들을 이용해서 값들을 출력하는 예제입니다.
public static void main(String[] args) {
ByteArrayOutputStream bos = null;
DataOutputStream dos = null;
byte[] result = null;
try {
bos = new ByteArrayOutputStream();
dos = new DataOutputStream(bos);
dos.writeInt(10);
dos.writeFloat(20.0f);
dos.writeBoolean(true);
result = bos.toByteArray();
System.out.println(bytesToHex(result));
System.out.println(Arrays.toString(result));
} catch (IOException e) {
e.printStackTrace();
}
}
static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString().trim();
}
00 00 00 0A 41 A0 00 00 01
[0, 0, 0, 10, 65, -96, 0, 0, 1]

코드를 실행해보면 위와 같은 결과가 출력되는 것을 알 수 있습니다.
기반 스트림인 ByteArrayOutputStream은 1바이트 단위로 기록하지만, DataOutputStream을 연결하면 자료형 크기(int=4바이트, float=4바이트 등)에 맞춰 바이트가 기록되었다는 것을 알 수 있습니다.
기록한 데이터를 읽을 때는 반드시 쓰인 순서대로 읽어야 합니다. int -> float -> boolean 순으로 기록했기 때문에 읽을 때도 동일하게 int -> float -> boolean 순으로 읽어야 합니다. 문자로 데이터를 저장하면, 다시 데이터를 읽어 올 때 문자들을 실제 값으로 변환하는, 예를 들어 문자 "100"을 숫자 100으로 변환하는 과정을 거쳐야하는 번거로움이 있습니다. 그러나 DataInputStream과 DataOutputStream을 사용하면 데이터를 변환할 필요도 없고, 자리수를 세어서 따지지 않아도 되므로 편리하고 빠르게 데이터를 저장하고 읽을 수 있습니다.
한 가지 주의할 점은 DataInputStream은 기본 자료형으로 읽기 때문에 다른 스트림처럼 더 이상 읽을 값이 없을 때 -1을 반환한다면 자료형의 값인지 끝난 것인지 구분할 수 없습니다. 따라서 다른 스트림과 다르게 더 이상 읽을 값이 없으면 -1을 반환하는게 아닌 EOFException을 반환하기 때문에 catch문에서 예외 처리를 추가해줘야 합니다.
즉, DataInputStream / DataOutputStream은 "기본 자료형(primitive type)"을 바이너리 형태로 안전하게 읽고 쓸 때 최적화된 보조 스트림입니다.
SequenceInputStream
SequenceInputStream은 여러 개의 입력 스트림을 연결해서 하나의 스트림으로부터 데이터를 읽는 것과 같이 처리할 수 있도록 도와줍니다. SequenceInputStream의 생성자를 제외하고 나머지 작업은 다른 입력 스트림과 다르지 않습니다. 큰 파일을 여러 개의 작은 파일로 나누었다가 하나의 파일로 합치는 것과 같은 작업을 수행할 때 사용하면 좋습니다.
SequenceInputStream은 다른 보조 스트림들과는 달리 FilterInputStream의 자손이 아닌, InputStream을 바로 상속받아서 구현하였습니다.
| 메서드 / 생성자 | 설 명 |
| SequenceInputStream(Enumeration e) | Enumeration에 저장된 순서대로 입력스트림을 하나의 스트림으로 연결합니다. |
| SequenceInputStream(InputStream s1, InputStream s2) | 두 개의 입력스트림을 하나로 연결합니다. |
Vector에 연결할 입력 스트림들을 저장한 다음 Vector의 Enumeration elements()를 호출해서 생성자의 매개변수로 사용합니다.
Vector files = new Vector();
files.add(new FileInputStream("file.001"));
files.add(new FileInputStream("file.002"));
SequenceInputStream in = new SequenceInputStream(files.elements());
또는 직접 두 개의 입력 스트림을 연결할 수도 있습니다.
FileInputStream file1 = new FileInputStream("file.001");
FileInputStream file1 = new FileInputStream("file.002");
SequenceInputStream in = new SequenceInputStream(file1, file2);
PrintStream
PrintStream은 데이터 기반 스트림에 다양한 형태로 출력할 수 있는 print, println, printf와 같은 메서드를 오버로딩하여 제공합니다. PrintStream은 데이터를 적절한 문자로 출력하는 것이기 때문에 문자 기반 스트림의 역할을 수행합니다. 그래서 JDK 1.1에서부터 PrintStream보다 향상된 기능의 문자 기반 스트림인 PrintWriter가 추가되었으나 그 동안 매우 빈번히 사용되던 System.out이 PrintStream이다 보니 둘 다 사용할 수밖에 없게 되었습니다.
PrintStream과 PrintWriter는 거의 같은 기능을 가지고 있지만 PrintWriter가 PrintStream에 비해 다양한 언어의 문자를 처리하는데 적합하기 때문에 가능하면 PrintWriter를 사용하는 것이 좋습니다.
System클래스의 static멤버인 out과 err, 즉 System.out과 System.err이 PrintStream입니다.
| 생성자 | 설 명 |
| PrintStream(OutputStream out) | 기본 OutputStream을 감싸서 PrintStream 기능 제공 |
| PrintStream(OutputStream out, boolean autoFlush) | autoFlush 옵션 제공 (println, printf, format 시 flush) |
| PrintStream(OutputStream out, boolean autoFlush, String encoding) | 인코딩을 지정한 문자 출력 지원 |
| PrintStream(File file) | 파일에 출력용 PrintStream 생성 |
| PrintStream(File file, String csn) | 파일 출력 + 문자 인코딩 지정 |
| PrintStream(String fileName) | 파일 이름으로 PrintStream 생성 |
| PrintStream(String fileName, String csn) | 파일 이름 + 인코딩 지정 |
| 메서드 | 설 명 | |
| void print(boolean b) void print(int i) void print(long l) void print(float f) void print(double d) void print(char c) void print(char[] s) void print(String s) void print(Object obj) |
void println(boolean b) void println(int i) void println(long l) void println(float f) void println(double d) void println(char c) void println(char[] s) void println(String s) void println(Object obj) |
boolean 출력 int 출력 long 출력 float 출력 double 출력 문자 출력 문자 배열 출력 문자열 출력 obj.toString() 출력 |
| void println() | 개행 출력 | |
| PrintStream printf(String format, Object... args) | 정형화된(formatted) 출력 | |
| boolean checkError() | 스트림을 flush하고 오류 여부 확인 | |
| protected void setError() | 작업 중에 오류가 발생했음을 알림 | |
| void writeBytes(byte[] buf) | 지정된 배열(buf)의 내용을 모두 스트림에 출력 (Java 14+) | |
print()나 println()을 이용해서 출력하는 중에 PrintStream의 기반 스트림에서 IOException이 발생하면 checkError()를 통해서 인지할 수 있습니다. println()이나 print()는 예외를 던지지 않고 내부에서 처리하도록 정의하였는데, 이는 매우 자주 사용되는 메서드이기 때문입니다. 만일 println()이 예외를 던지도록 정의되었다면 println()을 사용하는 모든 곳에 try-catch문을 사용해야 할 것입니다.
public class PrintStream extends FilterOutputStream
implements Appendable, Closeable {
private boolean trouble = false;
public void print(int i) {
write(String.valueOf(i));
}
private void write(String s) {
try {
...
} catch (IOException x) {
trouble = true;
}
}
}
printf()는 JDK 5부터 추가된 것으로, C언어와 같이 편리한 형식화된 출력을 지원하게 되었습니다.
다음은 자주 사용되는 옵션들입니다.
정수 출력 옵션
| format | 설명 | 예시 | 출력 |
| %d | 10진수 정수 | printf("%d", 10) | 10 |
| %5d | 최소 5칸, 오른쪽 정렬 | printf("%5d", 10) | 10 |
| %-5d | 최소 5칸, 왼쪽 정렬 | printf("%-5d", 10) | 10 |
| %05d | 0으로 왼쪽 채우기 | printf("%05d", 10) | 00010 |
| %,d | 천 단위 콤마 | printf("%,d", 10000) | 10,000 |
| %+d | 양수도 + 표시 | printf("%+d", 10) | +10 |
| %o | 8진수 | printf("%o", 10) | 12 |
| %x | 16진수(소문자) | printf("%x", 255) | ff |
| %X | 16진수(대문자) | printf("%X", 255) | FF |
문자열 출력 옵션
| format | 설명 | 예시 | 출력 |
| %s | 문자열 출력 | printf("%s", "Hi") | Hi |
| %10s | 최소 10칸, 오른쪽 정렬 | printf("%10s", "Hi") | Hi |
| %-10s | 최소 10칸, 왼쪽 정렬 | printf("%-10s", "Hi") | Hi |
| %.3s | 최대 3글자만 출력 | printf("%.3s", "Hello") | Hel |
| %10.3s | 최소 10칸 + 최대 3글자 | printf("%10.3s", "Hello") | Hel |
실수 출력 옵션
| format | 설명 | 예시 | 출력 |
| %f | 기본 실수 출력(소수점 6자리) | printf("%f", 3.14) | 3.140000 |
| %.2f | 소수 둘째 자리까지 | printf("%.2f", 3.14159) | 3.14 |
| %8.2f | 전체 8자리, 소수 2자리 | printf("%8.2f", 3.14) | 3.14 |
| %-8.2f | 왼쪽 정렬 | printf("%-8.2f", 3.14) | 3.14 |
| %,f | 천 단위 콤마 | printf("%,f", 12345.67) | 12,345.670000 |
| %e | 지수 표현(소문자) | printf("%e", 314.0) | 3.140000e+02 |
| %E | 지수 표현(대문자) | printf("%E", 314.0) | 3.140000E+02 |
| %g | 값에 따라 %f 또는 %e 자동 선택 | printf("%g", 0.0000314) | 3.14e-05 |
특수문자 출력 옵션
| format | 설명 | 예시 | 출력 |
| %% | % 문자 출력 | printf("%%") | % |
| %n | 줄바꿈(new line) | printf("Hello%nWorld") | Hello World |
| \t | 탭(tab) | printf("Hi\tThere") | Hi There |
| \b | 백스페이스 | printf("123\b4") | 124 |
| \' | 작은따옴표 | printf("\\'") | ' |
| \" | 큰따옴표 | printf("\\\"") | " |
| \\ | 백슬래시 | printf("\\\\") | \ |
문자 기반 스트림
문자 데이터를 다루는데 사용된다는 것을 제외하고는 바이트 기반 스트림과 문자 기반 스트림의 사용방법은 거의 같습니다.
Reader와 Writer
바이트 기반 스트림의 조상이 InputStream/OutputStream인 것과 같이 문자 기반의 스트림에서는 Reader/Writer가 그와 같은 역할을 합니다. 다음은 Reader/Writer의 메서드인데 byte배열 대신 char배열을 사용한다는 것 외에는 InputStream/OutputStream과 다르지 않습니다.
| 메서드 | 설 명 |
| int read() | 1개의 문자(2바이트, char)를 읽고 int로 반환 (EOF → -1) |
| int read(char[] cbuf) | 배열에 읽고 읽은 문자 수 반환 |
| abstract int read(char[] cbuf, int off, int len) | 부분 범위에 읽고 읽은 문자 수 반환 |
| int read(CharBuffer target) | 읽어서 문자버퍼(target)에 저장 |
| void mark(int readAheadLimit) | 마크 설정 (mark 지원할 때) |
| void reset() | mark 지점으로 되돌림 |
| boolean markSupported() | mark/reset 지원 여부 |
| long skip(long n) | n개의 문자 건너뛰기 |
| boolean ready() | 읽을 준비가 되었는지 확인 |
| abstract void close() | 스트림 닫기 |
| static Reader nullReader() | 아무 것도 읽을 수 없는 널 Reader를 반환 (Java 11+) |
| long transferTo(Writer out) | 모든 데이터를 Writer로 전송하고 전송한 데이터의 길이를 반환 (Java 10+) |
| 메서드 | 설 명 |
| void write(int c) | 한 문자 출력 (char 1개) |
| void write(char[] cbuf) | 문자 배열 출력 |
| abstract void write(char[] cbuf, int off, int len) | 배열 일부 출력 |
| void write(String str) | 문자열 출력 |
| void write(String str, int off, int len) | 문자열 일부 출력 |
| Writer append(char c) | 문자 추가 (write와 동일) |
| Writer append(CharSequence csq) | 문자열/문자열 빌더 추가 |
| Writer append(CharSequence csq, int start, int end) | 부분 문자열 추가 |
| abstractvoid flush() | 버퍼 비우기 |
| abstract void close() | 스트림 닫기 |
| static Writer nullWriter() | 널 Writer를 반환 (Java 11+) |
문자 기반 스트림이라는 것은 단순히 2byte로 스트림을 처리하는 것만을 의미하지는 않습니다. 문자 데이터를 다루는데 필요한 또 하나의 정보는 인코딩(encoding)입니다.
문자 기반 스트림, 즉 Reader/Writer 그리고 그 자손들은 여러 종류의 인코딩과 자바에서 사용하는 유니코드(UTF-16)간의 변환을 자동적으로 처리해줍니다. Reader는 특정 인코딩을 읽어서 유니코드로 변환하고, Writer는 유니코드를 특정 인코딩으로 변환하여 저장합니다.
FileReader와 FileWriter
FileReader/FileWriter는 파일로부터 텍스트 데이터를 읽고, 파일에 쓰는데 사용됩니다. 사용 방법은 FileInputStream/FileOutputStream과 다르지 않습니다.
public static void main(String[] args) {
try {
String fileName = "src/test.txt";
FileInputStream fis = new FileInputStream(fileName);
FileReader fr = new FileReader(fileName);
int data = 0;
while ((data = fis.read()) != -1) {
System.out.print((char)data);
}
System.out.println();
fis.close();
while ((data = fr.read()) != -1) {
System.out.print((char)data);
}
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
ìë íì¸ì!
안녕하세요!
결과에서도 알 수 있듯이, FileInputStream을 사용하면 1byte씩 읽어서 출력하므로 한글이 깨집니다. 반면 FileReader의 경우 한글이 제대로 출력됩니다.
PipedReader와 PipedWriter
PipedReader/PipedWriter는 쓰레드 간에 데이터를 주고받을 때 사용됩니다. 다른 스트림과는 달리 입력과 출력을 하나의 스트림으로 연결(connect)해서 데이터를 주고받는 다는 특징이 있습니다.
스트림을 생성한 다음에 어느 한 쪽 쓰레드에서 connect()를 호출해서 입력 스트림과 출력 스트림을 연결합니다. 입출력을 마친 후에는 어느 한 쪽 스트림만 닫아도 나머지 스트림은 자동으로 닫힙니다. 이 점을 제외하고는 일반 입출력 방법과 다르지 않습니다.
다음은 PipedWriter의 소스 코드로 생성자에 PipedReader를 인자로 전달하면 자동으로 connect()를 호출해줍니다. 이는 반대도 동일하게 동작합니다.
public class PipedWriter extends Writer {
private PipedReader sink;
public PipedWriter(PipedReader snk) throws IOException {
connect(snk);
}
...
}
다음과 같이 사용할 수 있습니다.
PipedReader pr = new PipedReader();
PipedWriter pw = new PipedWriter(pr);
Thread writerThread = new Thread(() -> {
try {
pw.write("Hello");
pw.close();
} catch (Exception e) {}
});
Thread readerThread = new Thread(() -> {
try {
int c;
while ((c = pr.read()) != -1) {
System.out.print((char)c);
}
} catch (Exception e) {}
});
writerThread.start();
readerThread.start();
PipedWriter의 write()는 내부적으로 연결된 PipedReader의 내부 파이프 버퍼에 데이터를 저장하고, PipedReader의 read()는 그 버퍼에서 데이터를 읽어갑니다.
StringReader와 StringWriter
StringReader/StringWriter는 CharArrayReader/CharArrayWriter와 같이 입출력 대상이 메모리인 스트림입니다. StringWriter에 출력되는 데이터는 내부의 StringBuffer에 저장되며 StringWriter의 다음과 같은 메서드를 이용해서 저장된 데이터를 얻을 수 있습니다.
StringBuffer getBuffer() // StringWriter에 출력한 데이터가 저장된 StringBuffer를 반환
String toString() // StringBuffer에 저장된 문자열을 반환
다음과 같이 사용할 수 있습니다.
public static void main(String[] args) throws IOException {
String inputData = "HELLO";
StringReader input = new StringReader(inputData);
StringWriter output = new StringWriter();
input.transferTo(output);
System.out.println(output); // toString()
}
문자 기반 보조스트림
BufferedReader와 BufferedWriter
BufferedReader와 BufferedWriter는 버퍼를 이용해서 입출력의 효율을 높일 수 있도록 해주는 역할을 합니다. 버퍼를 이용하면 입출력의 성능이 비교할 수 없을 정도로 높아지기 때문에 사용하는 것이 좋습니다.
BufferedReader의 readLine()을 사용하면 데이터를 라인 단위로 읽을 수 있고, BufferedWriter는 newLine()이라는 줄바꿈 해주는 메서드를 갖고 있습니다.
public static void main(String[] args) {
try {
FileReader fr = new FileReader("src/test.txt");
BufferedReader br = new BufferedReader(fr);
String line = "";
for (int i = 1; (line = br.readLine()) != null; i++) {
System.out.println(i + ": " + line);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
InputStreamReader와 OutputStreamWriter
InputStreamReader/OutputStreamWriter는 이름에서 알 수 있는 것과 같이 바이트 기반 스트림을 문자 기반 스트림으로 연결해주는 역할을 합니다. 예를 들어 인터넷 연결은 바이트 기반 스트림이라서 문자를 입출력하기에 불편하므로 바이트 기반 스트림을 Reader/Writer로 바꾸면 편리합니다. 이럴 때 InputStreamReader/OutputStreamWriter를 사용하면 좋습니다.
그리고 바이트 기반 스트림의 데이터를 지정된 인코딩의 문자 데이터로 변환하는 작업을 수행할 때 사용됩니다.
| InputStreamReader 생성자 / 메서드 | 설명 |
| InputStreamReader(InputStream in) | 플랫폼 기본 인코딩으로 byte → char 변환 |
| InputStreamReader(InputStream in, String charsetName) | 문자 인코딩을 문자열로 지정 |
| InputStreamReader(InputStream in, Charset charset) | Charset 객체로 인코딩 지정 |
| String getEncoding() | 사용 중인 문자 인코딩 반환 |
| OutputStreamWriter 생성자 / 메서드 | 설명 |
| OutputStreamWriter(OutputStream out) | 기본 플랫폼 인코딩으로 char → byte 변환 |
| OutputStreamWriter(OutputStream out, String charsetName) | 인코딩을 문자열로 지정 |
| OutputStreamWriter(OutputStream out, Charset charset) | Charset 객체로 인코딩 지정 |
| String getEncoding() | 사용 중인 문자 인코딩 반환 |
한글 윈도우에서 중국어로 작성된 파일을 읽을 때 `InputStreamReader(InputStream in, String encoding)`를 이용해서 인코딩이 중국어로 되어 있다는 것을 지정해주어야 파일의 내용이 깨지지 않고 올바르게 보일 것입니다. 인코딩을 지정해주지 않으면 OS에서 사용하는 인코딩을 이용해서 파일을 해석해서 보여 주기 때문에 원래 작성된 내용과 다르게 보일 것입니다.
이와 마찬가지로 OutputStreamWriter를 이용해서 파일에 텍스트 데이터를 저장할 때 생성자 `OutputStreamWriter(OutputStream out, String encoding)`를 이용해서 인코딩을 지정하지 않으면, OS에서 사용하는 인코딩으로 데이터를 저장할 것입니다.
public static void main(String[] args) {
String line = "";
try {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
System.out.println("OS Encoding: " + isr.getEncoding());
System.out.print("문장 입력 > ");
line = br.readLine();
System.out.println("내용: " + line);
} catch (IOException e) {
e.printStackTrace();
}
}
BufferedReader와 InputStream인 System.in을 연결하기 위해서 InputStreamReader를 사용한 예제입니다.
JDK 5부터는 Scanner가 추가되어 이와 같은 방식을 사용하지 않아도 간단하게 처리할 수 있습니다.
'Lang > Java' 카테고리의 다른 글
| [Java] 동기와 비동기, 블로킹과 논블로킹 (0) | 2025.11.24 |
|---|---|
| [Java 21] (17) - I/O 2 (0) | 2025.11.21 |
| [Java 21] (15) - Lambda & stream (0) | 2025.11.17 |
| [Java 21] (14) - thread 2 (0) | 2025.11.13 |
| [Java 21] (13) - thread 1 (0) | 2025.11.11 |