Technology/Java

Java의 입출력(I/O)에 대해 알아보자!

ikjo 2022. 10. 23. 05:10

입출력이란?

입출력(I/O)이란 입력(Input)과 출력(Output)을 줄여서 나타낸 것으로, 컴퓨터 내외부의 장치 또는 프로그램들 간의 데이터를 주고받는 것을 의미한다. 예를 들면, 키보드로 특정 데이터를 입력해서 화면에 출력시키는 것이 있으며, 또한 클라이언트와 서버간 데이터를 주고받는 웹 통신 역시 일종의 입출력에 해당된다.

 

이때 자바에서 입출력 시 기반이 되는 대표적인 것으로 스트림(Stream), 버퍼(Buffer), 채널(Channel)이 있다.

 

스트림(Stream) 기반의 입출력

자바 입출력에서의 스트림이란 데이터를 운반하는데 사용되는 연결 통로이다. 이때, 스트림은 단방향 통신만 가능하기 때문에, 입력과 출력을 동시에 처리할 수 없어 입력을 위한 입력 스트림과 출력을 위한 출력 스트림으로 구분된다. 또한, 스트림을 통해 전송한 데이터는 먼저 전송된 게 먼저 받게 되있으므로 마치 자료구조 큐(Queue)와 같이 연속적으로 데이터를 주고받는다.

 

버퍼(Buffer) 기반의 입출력

자바에선 기존 스트림의 기능을 보완하기 위해 (기능 향상 또는 새로운 기능 추가) 보조 스트림이 제공되는데, 보조 스트림만으로는 입출력을 처리할 수 없고, 기반 스트림을 먼저 생성한 다음에 이를 (기반 스트림) 이용해서 보조스트림을 생성해야한다. 이때 버퍼는 여러 보조 스트림 중 하나이다.

 

아래 코드는 콘솔(console)로부터 데이터를 읽어올 때 문자 보조 스트림인 BufferedReader을 사용함으로써 입력의 효율을 높인 것이다.

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        // ...
    }

 

버퍼는 입출력 성능 향상을 위해 사용된다. 한 바이트씩 입출력하는 것 보다 (많은 오버헤드 발생) 한 번에 여러 바이트를 입출력하는 것이 더욱 빠르기 때문이다. 그리하여 대부분의 입출력 작업에는 버퍼가 사용되고있다.

 

채널(Channel) 기반의 입출력

앞서 언급한 스트림 및 버퍼 기반의 입출력 관련 클래스들은 모두 java.io 패키지 내부에 속한다. 또한 이들은 입출력 시 블락킹(blocking) 상태에 있게 되는데, 이는 데이터를 읽어 올 때나 쓸 때 데이터를 기다리기 위해 멈춰있는 것(제어권을 잃음)을 뜻한다. 즉, 스트림 기반으로 입출력 작업을 진행하면 해당 쓰레드는 블락킹 상태에 있어 데이터 입출력이 끝날 때까지 작업을 중단하게 된다.

 

image source : https://developer.ibm.com/articles/l-async/

 

자바 4부터는 NIO(New Input Output)가 등장했다. 이는 java.nio 패키지 내부에 속하는 것들로, 채널(Channel)이 기반이 되는 입출력이다. 채널은 스트림과 다르게 양방향 통신이 가능하며, 입력 스트림과 출력 스트림처럼 입력과 출력을 구분하지않는다.

 

또한 Buffer라는 별도의 보조 스트림을 사용할 필요 없이 채널 자체는 버퍼를 통해 입출력하며, blocking 방식과 non-blocking 방식 모두 가능하다. non-blocking 방식을 사용하면 경우에 따라 block 방식의 문제점을 보완(효율적인 스레드 사용 등)할 수 있다.

 

 

바이트 스트림과 문자 스트림

스트림은 바이트(1byte) 단위로 데이터를 전송하는지, 문자(2byte) 단위로 데이터를 전송하는지에 따라 바이트 스트림문자 스트림으로 나뉜다.

 

참고로 문자 스트림의 존재 목적은 단순히 단위가 바이트가 아닌 문자라는 것에 그치는 것은 아니다. 문자 스트림의 또 다른 기능은 특정 인코딩 형식과 자바에서 사용하는 인코딩 형식간의 변환을 자동으로 처리해준다는 것이다.

 

※ 이번 글에서는 각각의  스트림 세부 특성들에 대해 다루진 않습니다!

 

바이트(Byte) 스트림 기반의 입출력

우선 바이트 스트림은 어떠한 대상에 대해 (바이트 단위로) 입출력 작업을 할 것인지에 따라 다양한 종류의 스트림이 있다.

 

이때 InputStream은 말그대로 어떠한 대상으로부터 (바이트 단위로) 입력을 받아오는 스트림인데, 어떠한 대상(오디오, 바이트 배열, 파일 등)으로부터 입력을 받아오냐에 따라 다음과 같이 InputStream을 상속받는 다양한 스트림 클래스들이 있다는 것을 확인할 수 있다. (다만, 이중에는 그 자체로만 사용될 수 없는 FilterInputStream 등 보조 스트림들도 있다.)

 

image source : https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html

 

이렇게 다양한 종류의 바이트 입력 스트림들이 존재하는데, 이들은 자신만의 입력 작업을 처리하기 위해 공통적으로 추상 클래스인 InputStream에 정의된 추상 메서드 read()를 구현한다.

 

InputStream 추상 클래스 中 read 추상 메서드


 

OutputStream 역시 말그대로 어떠한 대상에 (바이트 단위로) 출력하는 스트림인데, 어떠한 대상(오디오, 파일 등)에 출력하냐에 따라 다음과 같이 OutputStream을 상속받는 다양한 스트림 클래스들이 있다는 것을 확인할 수 있다. (다만, 이중에는 그 자체로만 사용될 수 없는 FilterOutputStream 등 보조 스트림들도 있다.)

 

image source : https://docs.oracle.com/javase/7/docs/api/java/io/OutputStream.html

 

마찬가지로 다양한 종류의 바이트 출력 스트림들이 존재하지만, 이들은 자신만의 출력 작업을 처리하기 위해 공통적으로 추상 클래스인 OutputStream에 정의된 추상 메서드 write(int b)를 구현한다.

 

OutputStream 추상 클래스 中 write(int b) 추상 메서드

 

문자(Character) 스트림 기반의 입출력

다음으로 문자 스트림 역시 어떠한 대상에 대해 (문자 단위로) 입출력 작업을 할 것인지에 따라 다양한 종류의 스트림이 있다.

 

이때 Reader는 어떠한 대상으로부터 (문자 단위로) 입력을 받아오는 스트림인데, 어떠한 대상(문자 배열 등)으로부터 입력을 받아오냐에 따라 다음과 같이 Reader를 상속받는 다양한 스트림 클래스들이 있다는 것을 확인할 수 있다. (다만, 이중에는 그 자체로만 사용될 수 없는 BufferedReader 등 보조 스트림들도 있다.)

 

image source : https://docs.oracle.com/javase/7/docs/api/java/io/Reader.html

 

위와 같이 다양한 종류의 문자 입력 스트림들이 존재하는데, 이들은 자신만의 입력 작업을 처리하기 위해 공통적으로 추상 클래스인 Reader에 정의된 추상 메서드 read(char[] cbuf, int off, int len)와 close()를 구현한다.

 

Reader 추상 클래스 中 read(char[] cbuf, int off, int len) 및 close() 추상 메서드


Writer는 어떠한 대상에 (문자 단위로) 출력하는 스트림인데, 어떠한 대상(문자 배열 등)에 출력하냐에 따라 다음과 같이 Writer를 상속받는 다양한 스트림 클래스들이 있다는 것을 확인할 수 있다. (다만, 이중에는 그 자체로만 사용될 수 없는 BufferedWriter 등 보조 스트림들도 있다.)

 

image source : https://docs.oracle.com/javase/7/docs/api/java/io/Writer.html

 

위와 같이 다양한 종류의 문자 출력 스트림들이 존재하는데, 이들은 자신만의 출력 작업을 처리하기 위해 공통적으로 추상 클래스인 Writer에 정의된 추상 메서드 write(char[] cbuf, int off, int len)와 close()를 구현한다.

 

Writer 추상 클래스 中 write(char[] cbuf, int off, int len) 및 close() 추상 메서드

 

 

표준 입출력

표준 입출력이란 콘솔(console)로부터 데이터를 입력받거나 콘솔에 데이터를 출력하는 것을 말한다. 자바에서는 표준 입출력을 위해 System 클래스의 정적 멤버 변수로 3가지의 입출력 스트림을 제공하는데, System.in과 System.out 그리고 System.err이다.

 

System 클래스 中 in, out, err 정적 멤버 변수

 

이때 InputStream 타입의 in은 콘솔로부터 데이터를 입력받는데 사용되며, PrintStream 타입의 out과 err는 콘솔에 데이터를 출력하는데 사용된다. 이들은 모두 null로 초기화되어있지만, 자바 애플리케이션이 실행될 때 자동으로 스트림이 생성되므로, 개발자가 별도로 스트림을 생성할 필요는 없다.

 

 

파일(File) 입출력

앞서 간략하게 다루었던 바이트 스트림과 문자 스트림에는 파일 대상의 입출력 스트림이 존재하는데, 바이트 스트림에는 FileInputStream과 FileOutputStream이 있으며, 문자 스트림에는 FileReader와 FileWriter가 있다.

 

아래 예시 코드에서는 문자 스트림 FileReader와 FileWriter를 이용하여 text 파일에 특정 데이터를 출력하고 이를 다시 읽어 콘솔 화면에 출력하도록 하고 있다.

 

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

class Test {

    public static void main(String[] args) {

        File file = new File("text.txt");

        try (FileWriter fileWriter = new FileWriter(file);
            FileReader fileReader = new FileReader(file)) {

            fileWriter.write("hello!");
            fileWriter.flush();

            System.out.println(fileReader.getEncoding()); // UTF-8

            int data;

            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }

        } catch (IOException e) {
            System.out.println("Error!!");
        }
    }
}

 

FileReader와 FileWriter의 경우 최초 객체 생성 시 별다른 인코딩 형식을 지정하지 않을 경우 자바 플랫폼의 인코딩 형식을 따르게 되있다. 현재 Test.java 파일의 인코딩 설정이 UTF-8로 설정되어있기 때문에 FileReader의 인코딩 형식이 UTF-8로 출력된 것을 확인할 수 있었다.

 

 

참고로 자바 11 이전에는 FileReader와 FileWriter에 특정 인코딩 형식 지정이 불가능했기 때문에, 이외 인코딩 형식 지정이 필요할 경우에는 다음과 같이 (FileReader 대신에) FileInputStream과 InputStreamReader을 써야만 했다.

 

    InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), encoding);

 

※ InputStreamReader의 경우에도 특정 인코딩 형식을 지정하지 않을 경우 자바 플랫폼의 인코딩 형식을 따른다.

 

 

참고자료

  • 도우출판 "자바의 정석"
  • https://javanitto.tistory.com/11