자바에서 I/O(Input/Output)는 프로그램이 외부 장치(키보드, 파일, 네트워크, 모니터 등)와 데이터를 주고받는 입출력 작업을 의미한다. 자바는 모든 입출력을 스트림(Stream)이라는 일관된 개념을 통해 처리하며, 데이터의 단위에 따라 바이트 기반 스트림과 문자 기반 스트림으로 구분하여 지원한다.
자바에서의 스트림은 데이터가 이동하는 통로(흐름)를 의미한다. (※ Java 8의 Stream API와는 다른 개념이다.)
그림, 음악, 동영상, 실행 파일 등 모든 종류의 이진 데이터(Binary Data)를 1바이트(byte) 단위로 입출력하는 스트림이다.
InputStream (입력), OutputStream (출력) - 둘 다 추상 클래스다.FileInputStream, FileOutputStream, ByteArrayInputStream, BufferedInputStream 등// 파일 복사 예시 (바이트 단위)
try (FileInputStream fis = new FileInputStream("input.png");
FileOutputStream fos = new FileOutputStream("output.png")) {
int data;
while ((data = fis.read()) != -1) { // 1바이트씩 읽기 (-1은 파일의 끝)
fos.write(data); // 1바이트씩 쓰기
}
} catch (IOException e) {
e.printStackTrace();
}
텍스트 문자를 입출력할 때 사용하는 스트림이다. 자바에서 char는 2바이트(Unicode)를 사용하므로, 1바이트 단위인 바이트 스트림으로 인코딩된 텍스트를 처리할 경우 깨짐 현상이 발생할 수 있다. 이를 방지하기 위해 2바이트(문자) 단위로 처리한다.
Reader (입력), Writer (출력) - 둘 다 추상 클래스다.FileReader, FileWriter, BufferedReader, BufferedWriter 등// 텍스트 파일 읽기 예시 (문자 단위)
try (FileReader fr = new FileReader("example.txt")) {
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
} catch (IOException e) {
e.printStackTrace();
}
결론부터 말하면 전혀 잘리지 않는다.
문자 기반 스트림이 "2바이트 단위로 처리한다"는 말은 파일을 2바이트씩 무작정 툭툭 잘라서 읽는다는 뜻이 아니기 때문이다.
여기서 '2바이트'는 파일의 저장 단위가 아니라, 자바 프로그램 내부(JVM 메모리)에서 문자를 다루는 기본 자료형인 char의 크기(2바이트, UTF-16)를 의미한다.
즉, "문자 기반 스트림(Reader)은 2바이트 단위로 처리한다"라는 말은:
"외부 파일이 1바이트든, 2바이트든, 3바이트든 상관없이, 인코딩 규칙에 맞게 해석해서 자바의 온전한 1글자(
char, 2바이트) 단위로 완성시켜서 반환해 준다"
라는 뜻이다.
FileReader나 InputStreamReader 같은 문자 기반 스트림 내부에는 디코더(Decoder, 인코딩 해석기)가 탑재되어 있다.
파일에 UTF-8로 저장된 한글 "가" (0xEA, 0xB0, 0x80 -> 총 3바이트)가 있을 때 문자 기반 스트림은 다음과 같이 동작한다.
0xEA)를 읽는다.0xB0, 0x80)까지 총 3바이트를 한꺼번에 묶어서 읽어 들인다. (여기서 데이터 손실이나 잘림이 발생하지 않는다.)char '가', Unicode 0xAC00)로 변환하여 출력해 준다.InputStream):(char)로 변환하면 글자가 깨진다.Reader):char(2바이트) 변수에 온전한 한 글자로 담아준다.UTF-8로 인코딩된 3바이트짜리 한글 데이터를 문자 기반 스트림으로 읽어도 잘리지 않는다.
문자 기반 스트림은 기계적으로 2바이트씩 잘라오는 것이 아니라, 파일의 인코딩(UTF-8)을 인식하여 3바이트를 올바르게 모은 후 자바의 문자인 char(2바이트) 단위로 완성해서 가져오기 때문이다.
보조 스트림은 실제 데이터를 직접 읽거나 쓰는 기능은 없지만, 다른 주 스트림(Primary Stream)에 연결하여 성능을 향상시키거나 추가적인 편의 기능을 제공하는 스트림이다.
BufferedInputStream / BufferedReader 등)내부 버퍼(Buffer)를 사용하여 데이터를 한 번에 묶어서 처리함으로써 입출력 횟수를 대폭 줄인다.
// BufferedReader를 이용한 라인 단위 텍스트 읽기
try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
String line;
while ((line = br.readLine()) != null) { // 줄 단위로 읽기
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
InputStreamReader / OutputStreamWriter)바이트 스트림을 문자 스트림으로 변환해 주는 역할을 한다. 표준 입력(System.in)처럼 바이트 단위로 들어오는 입력을 텍스트 데이터로 변환할 때 많이 쓰인다.
자바의 I/O는 데이터를 주고받는 단방향 통로인 스트림(Stream)을 기반으로 동작한다. 이미지나 바이너리 파일은 1바이트 단위의 InputStream/OutputStream을 사용하고, 텍스트 파일은 2바이트(문자) 단위의 Reader/Writer를 사용하는 것이 기본이다. 입출력 성능을 극대화하기 위해서는 BufferedReader와 같은 버퍼 기반의 보조 스트림을 조합하여 사용하는 것이 바람직하다.