자바 입출력과 스트림
프로그램은 데이터를 외부에서 받아 처리하고 출력하는 구조로 되어 있다. 프로그램으로 들어오는 모든 값을 Input이라 하고, 출력되는 값을 Output이라고 한다. 이를 입출력(I/O)라고도 부르며 자바에서는 입출력을 처리하기 위해 별도의 I/O 패키지를 제공한다.
여기서 데이터란, 자바 프로그램에서 처리할 수 있는 모든 데이터를 의미한다. 디스크상에 존재하는 데이터일 수도 있고, 키보드나 마우스와 같은 외부 입력장치에서 입력되는 데이터일 수도 있으며, 인터넷을 통해 전송되는 데이터일 수도 있다.
예를 들어 디스크에 있는 <read.txt> 파일을 읽어서 <write.txt> 파일을 쓴다고 가정한다면 우선 파일에 접근하기 위한 길이 필요할 것이다. 또한 읽어온 데이터를 쓰기 위한 길도 필요하다. 이렇듯 자바 프로그램이이 디스크에 접근해 데이터를 주고받는 작업을 도와주는 통로를 스트림 혹은 가상 통로라고 한다.
입출력을 위한 입력 스트림과 출력 스트림이 있다. 프로그램을 기준으로 데이터가 들어오면 입력 스트림이며 데이터가 나가면 출력 스트림이다. 입력과 출력이 동시에 진행되는 것이 아니라 각각 독립적으로 한 가지 기능만 동작하기 때문에 데이터를 교환하기 위해서는 입력 스트림과 출력 스트림이 따로 필요하다.
1. 스트림은 입출력 장치와 자바 프로그램 간의 연결 통로로 스트림의 양 끝에서 전달이 이루어진다.
2. 단방향 통신이므로 입력 스트림과 출력 스트림을 별도로 사용해야 하며 FIFO(First In First Out) 구조를 이루고 있다.
3. 연속된 데이터의 흐름으로 입출력 진행 시 다른 작업을 할 수 없는 상태가 된다.
4. 입출력 대상을 변경하기 편하며 동일한 프로그램 구조를 유지할 수 있다.
입출력 스트림의 종류
자바의 기본적인 데이터 입출력은 java.io 패키지에서 제공한다. java.io 패키지에서는 파일 시스템의 정보를 얻기 위한 File 클래스와 데이터 입출력을 위한 다양한 스트림 클래스를 제공하는데 스트림의 종류를 크게 분류하면 전달 방식에 따라 Byte 기반 스트림과 Char 기반 스트림으로 구분한다. Byte 기반 스트림은 데이터를 컴퓨터의 기본 단위인 byte 단위로 나누어 읽거나 쓰고, Char 기반 스트림은 텍스트 기반 문서를 다루기 위해 사용하는 스트림이다. 특징은 다음과 같다.
| Byte 기반 스트림 | Char 기반 스트림 |
| byte 단위로 파일을 읽고 출력할 때 사용 모든 타입의 데이터 가능 |
문자 단위로 파일을 읽고 출력할 때 사용 문자 데이터만 가능 |
Byte 기반 스트림
컴퓨터의 모든 데이터는 Byte 단위로 이루어져 있다. 따라서 Byte 기반 스트림의 경우 모든 타입의 데이터를 읽고 쓰는 것이 가능하다. 바이트 기반 스트림은 바이트 입력 스트림과 바이트 출력 스트림이 있다.
InputStream은 입력 스트림의 최상위 클래스로 제공되는 스트림이다. 해당 객체를 상속해 다양한 입력 스트림이 존재한다.

각자의 개발 목적에 맞게 선택해 사용할 수 있다.
FileInputStream은 파일에서 바이트 단위로 자료를 읽어들일 때 사용하는 스트림이다. 파일을 읽기 위한 FileInputStream 을 선언하는 방법은 다음과 같다.
new FileInputStream(경로/파일명);
매개변수로 파일명을 포함한 경로를 지정한다. 이미지, 동영상, 텍스트 등 모든 타입의 파일을 읽어올 수 있다.
FileInputStsream을 사용하면 파일을 읽어올 수 있다. 디스크에 저장된 문서 또는 인터넷상에서 받을 수 있는 문서들을 읽어서 출력할 수 있다. 예제를 위한 텍스트 파일을 만들 때는 프로젝트를 선택하고 파일을 만들어 저장한다. read() 메서드는 데이터를 1byte씩 읽어서 int 타입으로 리턴한다. 더 이상 읽을 내용이 없다면 -1을 반환하여 데이터를 다 읽은 시점을 알 수 있다.
read(byte[] b) 메서드는 데이터를 배열에 담아서 읽으므로 데이터를 읽는 횟수가 현저히 줄어든다. 따라서 데이터를 읽는 속도를 향상시킬 수 있다.
OutputStream은 출력 스트림의 최상위 클래스이다. 해당 객체를 상속해 다양한 출력스트림이 존재한다.

파일을 쓰기 위한 FileOutputStream을 선언하는 방법은 다음과 같다.
new FileOutputStream(경로/파일명, 이어쓰기 옵션);
파일을 쓸 때 기존 파일명이 존재하는 경우가 있다. 이때 해당 파일의 내용을 유지한 채 이어 쓰거나 기존의 내용을 무시하고 새롭게 파일을 생성할 수 있다. 이어 쓰기 옵션이 true이면 기존 파일에 이어서 내용을 추가하고, false이면 기존 내용을 무시하고 새로 쓰게 된다. 기본 옵션은 false이다.
write(int b) 메서드는 1byte 단위로 데이터를 받아 출력하는 메서드이다. 단일 데이터를 매개변수로 받아서 출력 대상에 쓰게 된다.
write(byte[] b) 메서드는 파일을 쓸 때, 배열을 통해서 여러 데이터를 한 번에 출력할 수 있다. 많은 양의 데이터를 파일에 저장할 때 유용하다.
문자 기반 스트림
자바에서는 기본 자료형이 char 형을 통해 문자를 저장할 수 있다. 1byte 단위로 처리하는 바이트 기반 스트림은 모든 파일을 다룰 수 있으나 문자를 처리하는 char형의 크기는 2byte로 별도의 처리를 하지 않으면 정상적으로 읽지 못하는 경우가 있다. 이때, 문자 기반 스트림을 사용하면 간단하게 문자를 처리할 수 있다. 문자 기반 스트림은 문자 입력 스트림과 문자 출력 스트림이 있다.

| 메서드 | 설명 |
| int read() | 1개의 문자를 읽고 반환 더 이상 읽을 문자가 없으면 -1을 반환 |
| int read(char[] cbuf) | 매개변수로 주어진 배열에 읽은 문자를 저장하고 읽은 수만큼 반환 더 이상 읽을 문자가 없으면 -1을 반환 |
| int read(char[] cbuf, int offset, int len) | 매개변수로 주어진 배열에 정해진 범위만큼 읽어서 저장 시작 위치(offset), 길이(len) |
| close() | 스트림 사용을 종료하고 자원을 반환 |
FileReader는 앞에서 FileInputStream의 기능들과 메서드의 이름이 같다. 기능의 사용법도 크게 다르지 않는다.
FileInputStream에서 사용한 예제의 경우 read() 메서드를 통해서 데이터를 읽어오면 한글이 깨져서 정상적으로 출력되지 않았다. 그러나 FileReader를 사용하면 한글까지 정상 출력되는 것을 확인할 수 있다. FileReader는 read()는 바이트 단위가 아닌 문자 단위로 읽어오기 때문이다.
FileReader 스트림의 read(char[] cbuf) 메서드는 FileInputStream의 내용과 동일하다. 다른 점은 byte 배열이 아닌 char 배열을 매개변수로 사용한다는 점이다. 한 글자씩 읽는 read() 메서드에 비해 배열 단위로 문자를 읽기 때문에 읽는 횟수가 현저히 줄어들어 속도에서 효율성을 높인다.
문자 기반의 출력 스트림은 최상위 클래스 Writer를 상속해 다양한 클래스를 제공한다.

| 메서드 | 설명 |
| int write(char) | 단일 문자 출력 |
| int write(char[]) | 매개변수로 주어진 배열의 모든 문자 출력 |
| int write(String) | 문자열을 매개변수로 주어진 문자열 출력 |
| int write(char[], int offset, int len) | 매개변수로 주어진 배열에 정해진 범위만큼 읽어서 출력 시작 위치(offset), 길이(len) |
| void flush() | 출력 버퍼에 잔류하는 모든 내용 출력 |
| close() | 스트림 사용을 종료하고 자원 반환 |
파일을 출력하기 위한 FileWriter를 선언하는 방법은 다음과 같다.
new FileWriter(경로/파일명, 이어쓰기 옵션);
FileWrite도 FileOutputStream과 마찬가지로 파일을 생성할 때, 파일의 내용을 유지한 채 이어 쓰거나 기존 내용을 무시하고 새롭게 파일을 생성할 수 있다. 이어쓰기 옵션이 true이면 기존 파일에 이어서 내용을 추가하고, false이면 기존 내용을 무시하고 새로 쓰게 된다. 기본 옵션은 false로 되어 있다.
write(char cbuf)는 단일 문자를 입력 받아 출력하는 메서드이다. 한 문장씩 들여서 지정된 파일에 내용을 입력하게 된다.
보조 스트림
스트림의 기능에 따라 다음과 같이 기반 스트림과 보조 스트림으로 구분한다.
- 기반 스트림 : 대상에 직접 자료를 읽고 쓰는 스트림이다.
- 보조 스트림 : 직접 읽고 쓰는 기능 없이 기반 스트림에 추가로 사용할 수 있는 스트림이다.
보조 스트림은 실제로 데이터를 주고받을 수 없지만, 스트림의 기능을 향상시키거나 새로운 기능을 제공해 주는 스트림으로 다른 보조 스트림과 중첩하여 사용할 수 있다. 프로그램은 입력 스트림을 통해 직접 데이터를 읽지 않고 보조 스트림을 통해 전달받는다. 또한 출력 스트림을 ㅗ직접 데이터를 보내지 않고 보조 스트림을 통해 전달받아 데이터를 출력한다.
보조 스트림을 사용하려면 보조 스트림을 매개변수로 받는 기반 스트림이 먼저 선언되어야 한다. 보조 스트림은 스스로 데이터를 읽거나 쓸 수 없기 때문에 입출력과 바로 연결되는 기반 스트림이 필요하다. 선언하는 방법은 다음과 같다.
보조 스트림 변수명 = new 보조 스트림(기반 스트림);
느린 하드디스크와 네트워크는 입출력 성능에 영향을 준다. 이때 입출력 소스와 직접 작업하지 않고 버퍼라는 메모리를 이용해 작업하면 실행 성능을 향상시킬 수 있다. 하지만 버퍼는 크기가 작아 많은 양의 데이터를 처리하기에는 부족하다. 그래서 보조 스트림 중에서는 다음과 같이 메모리 버퍼를 추가로 제공해 스트림의 성능을 향상시키는 것들이 있다.
- 바이트 기반 스트림 : BufferedInputStream과 BufferedOutputStream
- 문자 기반 스트림 : BufferedReader와 BufferdWrite
BufferedInputStream은 바이트 기반 스트림인 InputStream에 연결되어 버퍼를 제공해 주는 보조 스트림이고 BufferedOutputStream은 OutputStream에 연결되어 버퍼를 제공해 주는 보조 스트림이다. 프로그램은 입력 장치로부터 데이터를 직접 읽는 대신 버퍼에 저장해 두었다가 큰 단위로 받기 때문에 속도가 향상된다. 또한 출력에 대한 보조 스팀은 프로그램에서 전송한 데이터를 내부 버퍼에 쌓아두었다가 버퍼가 꽉 차면, 모든 데이터를 한꺼번에 전송한다.
보조 스트림의 객체 선언은 기반 스트림을 생성자의 매개변수로 하여 선언한다.
InputStream in = new ...
BufferdInputStream bis = new BufferdInputStream (in);
BufferedReader는 문자 기반 스트림인 Reader에, BufferedWriter는 Writer에 연결되어 버퍼를 제공해주는 보조 스트림이다. 바이트 기반 스트림과 마찬가지로 보조 스트림을 사용해 성능을 향상시킬 수 있다. BufferedReader 또는 BufferedWriter의 경우, 버퍼에 데이터를 저장하여 입력 또는 출력하기 때문에 한 단어뿐만 아니라 문장 단위로 데이터를 읽거나 쓸 수 있다.
바이트 기반 스트림으로 텍스트 파일을 읽거나 쓸 경우, 한글을 포함한 비영어권 문자들이 정상적으로 출력되지 않았다. 소스 스트림이 바이트 기반 스트림이고, 입출력 데이터가 문자라면 Reader와 Writer로 변환하여 사용하는 것을 고려해야 한다. 그 이유는 Reader와 Writer는 문자 단위로 입출력하기 때문에 바이트 기반 스트림보다 다양한 문자를 입출력할 수 있기 때문이다. 문자 변환 보조 스트림에는 InputStreamReader와 OutputStreamWriter가 있다.
InputStreamReader는 바이트 기반 스트림 InputStream을 문자 기반 스트림 Reader로 변환하는 보조 스트림이다.
FileInputStream in = new ...;
InputStreamReader is = new InputStreamReader(in);
InputStreamReader is = new InputStreamReader(in, text-ending);
InputStreamReader를 선언할 때는 text-encoding을 선택해 선언할 수 있다. 개발 환경의 text-encoding이 기본으로 지정되어 사용된다. 해당 인코딩을 file이 생성될 때 사용한 것과 동일하게 지정되어야 하므로 만약 개발 환경이 읽어 들이는 파일의 text-encoding과 다르다면 직접 지정해야 한다.
OutputStreamWriter는 바이트 기반 스트림 OutputStream을 문자 기반 스트림 Writer로 변환하는 보조 스트림이다.
FileOutputStream out = new ... ;
OutputStreamWriter is = new OutputStreamWriter(out);
OutputStreamWriter is = new OutputStreamWriter(in, text-encoding);
OutputStreamWriter를 선언할 때도 text-encoding을 선택해 선언할 수 있다. 생성되는 파일의 text-encoding을 결정한다.
File 클래스
파일 객체를 선언하는 방법은 기존에 하긋ㅂ했던 선언과 크게 다르지 않다. 다만 생성자의 매개변수로 파일의 경로를 받는다. 파일의 경로를 작성할 때 사용하는 구분자는 운영체제에 따라 조금씩 다르다. 윈도우 환경의 경우 \(역슬래시) 또는 /(슬래시) 모두 가능하지만 \를 사용할 경우 2개를 작성해야 한다.
직렬화
자바 시스템 내부에서 사용하는 객체나 데이터를 외부의 시스템에서 사용할 수 있도록 바이트 단위의 데이터로 변환시키는 기술을 말한다. 즉, 클래스 형태의 데이터를 바이트 단위로 변환한다. 바이트 단위로 변환된 데이터를 다시 객체 형태로 변환하는 것을 역직렬화라고 한다.
직렬화 기능을 사용하기 위해서는 먼저 사용할 객체를 준비해야 한다. 사용할 객체에 java.io.Serializable 인터페이스를 상속하여 직렬화가 가능한 객체로 만들 수 있다.
serialVersionUID는 객체를 직렬화 또는 역직렬화 할 때 서로 값이 맞는지를 확인하는 기준이 되는 값이다. 만약 직려로하하는 과정에서 해당 값이 변경된다면 InvalidClassException 예외가 발생한다. 물론 해당 값을 필수 값이 아니므로 지정하지 않는다면 클래스의 해시 값을 사용하게 된다.
Serialzable을 상속한 객체를 직렬화하여 전달하기 위해 객체 보조 스트림인 ObjectInputStream과 ObjectOutputStream을 사용한다.
- ObjectOutputStream : 직렬화, 객체를 저장하기 위해 사용한다.
- ObjectOutputStream : 역직렬화, 객체를 읽기 위해 사용한다.
'BOOK' 카테고리의 다른 글
| [HTML]Chapter3-1 (1) | 2025.02.22 |
|---|---|
| [HTML] Chapter 1 (1) | 2025.02.19 |
| [JAVA] MENTOR JAVA SECTION 18 (1) | 2025.01.25 |
| [JAVA] MENTOR JAVA SECTION 17 (3) | 2025.01.24 |
| [JAVA] MENTOR JAVA SECTION 16 (2) | 2025.01.23 |