JAVA - 13 : I/O

13 I/O


기본적으로 IO란 Input/Output의 약자이며, 컴퓨터 내부 또는 외부 장치와 프로그램간의 데이터를 주고 받는 것을 의미한다.

  • 키보드를 사용하여 구글 검색창에 단어 입력
  • 게임 그래픽이 화면에 출력

13.1 스트림 (Stream) & 버퍼 (Buffer) & 채널 (Channel)

13.1.1 스트림(Stream)

  • 자바 IO의 기반
  • 출발지와 도착지
  • 단방향
  • 데이터의 순서 보존(FIFO)
  • 입력 스트림, 출력 스트림

자바 IO의 기반인 스트림에 대해 알아보자. 우선, 스트림(Stream)을 우리가 친숙한 한국어로 해석하면 시냇물이 된다. 그리고 이것이 의미하는 것은 출발지와 도착지가 명확하고 데이터의 순서가 보존되는 실시간 단방향 데이터 전송 흐름이다. 왜냐하면, 시냇물이 흐르기 위해서는 출발지와 도착지가 필요하고, 실시간으로 흐르는 물의 방향은 정해져 있다. 그렇기 때문에 컴퓨터에는 입력 스트림과 출력 스트림이 따로 존재한다.

여기서 출발지는 Source 도착지는 Destination(또는 Sink) 이라 표현한다. 실행 중인 프로그램을 기준으로 특정 출발지로부터 입력을 받기 위해 입력 스트림을 만들고 데이터를 받아들이고, 출력을 할 때는 특정 도착지로 출력 스트림을 만들어 데이터를 보내는 것이 실시간으로 이루어진다.

13.1.2 버퍼(Buffer)

  • 스트림 속 데이터 임시 저장 공간, 메모리 차지 단점
  • IO 시스템콜 호출 빈도 감소로 상대적인 성능 향상 기대
  • 처리해야하는 데이터 양이 클수록 효과적
  • 가득 차거나 특정 조건 만족 시 버퍼 내 데이터 일괄 처리

버퍼(Buffer)는 우리말로 번역하면 '완충'이라 해석된다. 이때 데이터의 입출력에서 완충이 의미하는 것이 무엇인지 알아야 한다. 기존 스트림은 순서가 보장된 데이터의 실시간 단방향 흐름이다. 하지만, 여기서 문제가 발생한다. 실시간으로 데이터를 주고받기 위한 전제로 스트림에서 프로그램은 데이터의 양과 상관없이 항상 IO 시스템콜을 해야한다. IO 자체가 프로그램이 실행중인 메인 메모리보다 한참이나 속도가 느린 보조기억장치나 외부 장치와 데이터를 주고 받는 것이기 때문에 프로그램이 실행 중일 때 IO 시스템콜이 빈번할 수록 프로그램의 성능이 안 좋아진다.

이때 버퍼를 사용하면 IO 시스템콜의 빈도가 줄어들게 되어 상대적으로 입출력에 대한 성능의 향상을 기대할 수 있다. 왜냐하면 버퍼는 완충 지역으로서 스트림 속 데이터 임시 저장 공간이라 보면 된다. 그리고 이 버퍼에 데이터가 가득 차거나 특정 조건을 만족하면 버퍼를 비우면서 안에 있던 데이터들을 한번에 입력 또는 출력시킨다.

13.1.3 채널(Channel)

  • 자바 NIO
  • 양방향 통신
  • 버퍼 활용

채널(Channel)은 자바4에 추가된 java.nio 패키지를 통해 새롭게 도입된 양방향 데이터 통로이다. 아래의 그림을 보자.

여기서 유추할 수 있는 점은 아래와 같다.

  • Source와 Sink가 동일 가능
  • Input과 Output을 위한 별도의 Buffer가 각각 존재
  • Buffer에 직접적으로 데이터 쓰기 및 읽기

채널과 관련한 내용은 java.nio 패키지와 관련해 자세한 설명이 필요하기 때문에 따로 포스트를 작성하였다.
-> 자바 NIO 포스트 보러가기

13.1.4 직렬화(Serialization)

마지막으로 입출력과 관련하여 다룰 내용은 직렬화(Serialization)이다. 주의할 점은 직렬화는 자바 IO 만의 특징이 아니다. 직렬화는 어떠한 Source와 Sink 사이에서도 어떤 데이터 타입(구조, 형식)이라도 원본 훼손없이 온전한 전달을 위해 고안된 개념이다. 이때 Serialize 의 뜻을 알면 더 명확해진다.

즉, Serialization은 "연속된 데이터 나열"이 된다. 이말인 즉, 전송하고자 하는 데이터는 형태와 상관없이 연속된 데이터의 나열이 된다는 것을 의미한다. 그리고 아래의 그림처럼 Sink 와 Source는 다른 프로그램 또는 장치여도 무관하다.

13.3 데이터 타입과 스트림

13.3.1 표준 스트림 (System.in, System.out, System.err)

  • in, out, err 제공
  • 콘솔 기준 버퍼 스트림
  • 콘솔에서 대상 변경 가능

표준 입출력 스트림은 콘솔을 통한 데이터 입력과 출력을 의미하고 총 3가지의 스트림을 제공한다.

  • System.in : 콘솔로부터 입력 제공
  • System.out : 콘솔로 출력 제공
  • System.err : 콘솔로 에러 출력 제공

그리고 System 클래스 내부를 살펴보면 in, out, err 각각은 아래와 같이 스트림 타입으로 정의되어있다.

public static final InputStream in = null;
public static final PrintStream out = null;
public static final PrintStream err = null;

그리고 PrintStream 의 생성자는 OutputStream 라는 것을 매개로 받는다.

public PrintStream(OutputStream out) {this(out, false);}

결국 표준 스트림을 이해하기 위해서는 InputStreamOutputStream에 대해 알아야 한다.

참고로 표준 입출력 스트림은 모두 버퍼를 활용한다.

그리고 표준 입출력의 대상을 setOut(),setErr(),setIn() 을 활용해 콘솔이 아닌 곳으로 변경도 가능하다.

Scanner sc = new Scanner(System.in);
System.out.println(); 

참고로, 우리가 일반적으로 입력과 출력을 하기 위해 하던 방식이 어떻게 동작하였는지도 알 수 있다.

13.3.2 InputStream & OutputStream

  • 1 바이트 단위 데이터 입출력, 한글 깨짐, 주로 미디어 파일 활용
  • 추상 클래스, 모든 입출력 스트림의 부모 클래스
  • 표준 입출력 스트림은 구현 클래스
  • read(), write()
  • InputStream은 기본적으로 버퍼(바이트 배열) 활용, OutputStream 에 flush() 선언

이 둘은 바이트 단위의 입출력 스트림이며 모든 입출력 스트림의 부모가 되는 클래스이다. 그리고 InputStream은 기본적으로 버퍼를 활용하며 바이트 단위로 데이터를 읽는다.

그리고 InputStream 에서 read() 메소드를 활용하면 읽어들인 바이트의 개수를 반환한다.
아래는 결과이다.

여기서 주의할 점은 InputStream 은 read() 를 따로 구현해주어야 하는데 return 값을 -1 로 설정하면 바이트를 읽어들이지 않고 항상 -1 을 반환한다.

OutputStream 같은 경우는 write() 메소드에 정수 타입 매개인자로 전송할 바이트의 개수를 정한다. 그리고 버퍼링에 활용되는 메소드인 flush() 가 선언되어 있다.

flush() 는 스트림 속 버퍼에 존재하는 데이터를 Source 로 보내어 버퍼를 비워주는 메소드이다. 하지만, OutputStream 에서는 아무런 기능을 하지 않는다.

하지만 이 둘은 추상클래스이기 때문에 실제로는 new 연산자를 사용해서 객체를 만드는 방법이 대신 아래와 같이 표준 입출력 스트림을 이용한다.
아래는 실행 결과이다.

여기서 우리는 표준 입출력 스트림이 InputStream 이나 OutputStream 의 구현 클래스라는 것을 알 수 있다.

13.3.3 Reader & Writer

  • 2 바이트(char) 단위 데이터 입출력, 한글 깨짐 x
  • 문자 기반 입출력 스트림의 최상위 추상 클래스
  • 버퍼(문자 배열) 활용, 바이트 -> 문자 변환
  • Reader는 java.nio 패키지에도 사용 가능

Reader와 Writer는 InputStream과 OutputStream 과 크게 다른 점은 일단 입력과 출력의 데이터 단위가 2 바이트 즉, 문자 단위라는 점이다. 그리고 둘 다 버퍼를 활용하는데 이는 바이트를 문자로 변환하기 위한 목적이다. 특히, Reader의 경우 버퍼를 활용하고 있으며 java.nio 패키지의 charBuffer 클래스에도 사용 가능하다.

하지만 이 둘은 추상클래스이기 때문에 실제로는 이들을 보조 스트림인 InputStreamReader() 와 OutputStreamWriter()를 사용한다.
아래는 실행 결과이다.

13.3.5 BufferedReader() & BufferedWriter()

  • 보조 스트림, 기본 스트림에 특성이나 기능을 추가해서 사용하는 방식
  • 문자열 단위로 데이터 입출력, 주로 콘솔에서 사용
  • 기본적으로 readLine() 활용, 엔터(라인) 기준으로 데이터 입력 구분, 기타 공백은 무시
  • write() + flush() 를 통해 콘솔 출력, 자동개행 없음, newLine()으로 개행문 추가 가능

아래는 두 스트림을 활용하는 간단한 예제이다.
아래는 실행 결과이다.

13.3.6 보조 스트림

  • 데코레이터 패턴(서브 클래싱 X),
  • 대상 클래스를 필드에 선언 및 기능 추가한 클래스

위에서 BufferedReader() 나 BufferedWriter(), 2 개의 보조 스트림에 대한 소개와 설명을 하였다. 하지만 자바는 데이터 타입 또는 Source, Sink 의 특성을 고려해 다양한 보조 스트림을 제공한다.

파일 작성을 위한 스트림과 객체 정보를 전달하기 위한 스트림 등 다양한 보조 스트림이 있다. 하지만, 여기서는 다루지 않고 관련 스트림 등은 공식 문서를 통해서 확인하기로 하자.

13.4 파일 IO

13.4.1 파일 읽기

마지막으로, 앞서 보조 스트림에 대한 소개를 하였는데 그 중 중요도가 높은 파일 IO 를 설명하고자 한다. 아래는 예시로 사용할 텍스트 파일test.txt이며 영어와 한글이 혼합된 내용이다.

그리고 아래는 각각의 FileReader 와 FileInputStream 을 활용해 파일을 읽어드리는 예시이다.

아래는 실행 결과이다.

FileInputStreamInputStream 을 활용하는 것이기 때문에 1 바이트 단위로 데이터를 읽는다. 그래서 한글이 깨져서 출력되는 것을 확인할 수 있다. 즉, 파일을 읽을 때 Reader로 읽을지 InputStream으로 읽을지도 고려해야 한다.

13.4.2 파일 쓰기

파일 중 텍스트 파일에 데이터를 쓰는 예시를 대표적으로 보면 아래와 같다.

그리고 그 결과는 아래와 같이 정상적으로 실행된 것을 확인할 수 있다.

주의할 점은, 여기서도 어떤 타입의 데이터를 어떤 형태의 파일로 만들 것인지를 고려하여 적절한 스트림을 사용해야 한다.

좋은 웹페이지 즐겨찾기