톰캣 미션을 수행하며, Java에서 Socket을 사용할 때 InputStream / OutputStream을 활용해 데이터를 주고받는다는 사실을 알게되었다.
이번 포스팅에서는 Java I/O Stream의 개념에 대해 알아보고자한다.
Stream
Stream은 데이터가 이동하는 통로이다. 데이터는 FIFO형태로 전송된다.
Stream의 가장 큰 특징은 단방향으로만 흘러간다는 점인데, 하나의 스트림으로 입출력을 같이 처리할 수 없어 입출력을 위해서는 두 개의 스트림(InputStream/OutputStream)이 필요하다.
InputStream은 외부에서 데이터를 읽는(입력) 역할을 수행하며, OuputStream은 외부로 데이터를 출력하는 역할을 수행한다.
Stream은 처리하는 데이터의 유형에 따라 크게 두 가지 유형으로 나뉜다.
바이트 기반 스트림 (Byte Streams)
데이터를 바이트 단위(8bit)로 처리하는 스트림이다.
이들은 주로 이진 데이터(이미지, 동영상, 파일 등)를 다룰 때 사용된다.
추상 클래스 InputStream, OutputStream를 상속하여 구현한다.
InputStream
추상 클래스 InputStream에는 추상 메서드 read가 정의되어있다.
각 구현체는 해당 메서드를 구현하여 입력 스트림으로부터 데이터를 읽어오는 방법을 정의한다.
OutputStream
추상 클래서 OutputStream에는 추상 메서드 write가 정의되어있다.
각 구현체는 해당 메서드를 구현하여 출력 스트림에 데이터를 쓰는 방법을 정의한다.
한글 처리
1byte는 범위가 0~255로 알파벳의 대/소문자 아스키 코드 값은 모두 해당 범위에 속한다.
하지만 한글의 아스키 코드 값을 나타내려면 2byte가 필요하고, 따라서 ByteStream에서는 한글이 깨진다.
한글 File을 읽고 쓸 때는 Character Streams(Reader/Writer)를 활용해야한다.
문자 기반 스트림 (Character Streams)
데이터를 문자 단위로 처리하는 스트림이다. (Java의 char 자료형 크기인 2byte 단위로 데이터를 처리한다.)
이들은 주로 텍스트 데이터를 다룰 때 사용되며, 다양한 문자 인코딩(ex. UTF-8)을 처리할 수 있다.
추상 클래스 Reader, Writer를 상속하여 구현한다.
바이트 기반 스트림에서 InputStream을 Reader로, OutputStream을 Wirter로 변경해주면 문자 기반 스트림을 생성할 수 있다.
보조 스트림
기반 스트림은 데이터를 실제로 입출력 하는데 사용된다. (위에서 알아본 스트림들)
이와 달리 보조 스트림은 직접적으로 리소스와 연결되는게 아닌, 다른 기반 스트림을 래핑하고 추가적인 기능을 제공한다. (즉, 혼자 쓰일 수 없고 다른 기반 스트림과 함께 사용되어야한다.)
추가적인 기능으로는 버퍼링, 암호화, 압축, 포맷 변환 등이 있다.
추상 클래스 FilterInputStream, OutputStream, FilterReader, FilterWirter를 상속하여 구현한다.
String text = "애쉬";
// 1. 기반 스트림 생성
InputStream inputStream = new ByteArrayInputStream(text.getBytes());
// 2. 보조 스트림 - 기반 스트링 래핑
InputStream bufferedInputStream = new BufferedInputStream(inputStream);
// 3. 보조 스트림으로부터 데이터 읽어오기
byte[] actual = bufferedInputStream.readAllBytes();
// True
assertThat(actual).isEqualTo("애쉬".getBytes());
BufferedInputStream / BufferedOutputStream
기반 스트림은 1byte씩 입/출력을 하기 때문에 시간이 오래걸린다.
BufferedInputStream/BufferedOutputStream은 데이터를 읽고 쓸 때 버퍼링을 제공하여 입력 속도를 빠르게 해준다.
이는 내부에 고정 크기의 바이트 버퍼(기본 8192byte)를 가지고 있다.
BufferedInputStream
read 메서드를 호출하면 입력 소스로부터 한 번에 여러 바이트의 데이터를 읽어와 버퍼에 저장한다.
Java App에서 읽을 때 마다 버퍼(메모리)에서 데이터를 제공하고, 이로 인해 외부 입력 소스에 접근하는 횟수가 줄어 작업의 효율이 크게 향상된다.
버퍼의 데이터를 모두 읽으면, 다시 한 번에 여러 바이트의 데이터를 읽어와 버퍼를 채운다.
BufferedOutputStream
write 메서드를 호출하면 내부 버퍼에 출력할 데이터를 모은다.
버퍼가 가득 찰 때 까지 데이터를 모은 후, 버퍼가 가득 차면 한꺼번에 출력 대상으로 전송해 버퍼를 비운다. 이로 인해 외부 출력 대상에 접근하는 횟수가 줄어 작업의 효율이 크게 향상된다.
버퍼가 가득차지 않은 상황에서 flush 메서드를 호출하면, 버퍼의 데이터가 강제로 출력 대상으로 전송되고 버퍼가 비워진다.
BufferedReader/BufferedWriter도 동일한 동작 방식을 가진다.
참고 자료
https://yeoonjae.tistory.com/entry/JavaJava-에서의-IO입출력-Stream
'프로그래밍' 카테고리의 다른 글
[Java] Thread란? (0) | 2023.09.13 |
---|---|
Facade 객체를 활용해 트랜잭션에서 외부 API 통신 분리하기 (4) | 2023.09.11 |
[Spring] @Async와 스레드풀 (4) | 2023.08.25 |
[Spring] Oauth2 소셜 로그인 확장에 유리하게 개선하기 (Kakao, Google, Naver) (1) | 2023.08.23 |
[Docker / MySQL] Docker로 띄운 mySQL 컨테이너에 employee sample DB 적재하기 (2) | 2023.08.15 |