Java 입출력은 어떤걸 사용해야하나?

maketheworldwise·2022년 7월 23일
0


이 글의 목적?

백준 문제를 풀면서 입력값을 받아와야하는 상황이 발생했다. 사용자의 입력을 받아서 처리할 수 있는 방법은 2가지가 있는데, 어떤걸 사용해야할까? (추가로 출력하는 방법도 함께 정리하자!)

입출력 스트림

입출력은 컴퓨터와 프로그램간 데이터를 주고 받는 것을 의미하고, 자바에서는 이를 위해 스트림이라는 개념을 사용한다. 스트림이라는 의미는 네트워크에서 자료의 흐름이 물과 같다는 의미에서 따왔다. 한 방향으로만 흐르기 때문에 단방향 통신으로 입출력을 동시에 수행할 수 없다. 따라서 입력 스트림과 출력 스트림 총 2개의 스트림이 필요하다.

스트림은 먼저 보낸 데이터를 먼저 받는 큐의 FIFO 구조와 유사하게 동작하며, 입출력 스트림 종류를 구분하는 기준은 총 3가지다.

종류IO 대상 기준자료의 종류 기준스트림의 기능 기준
FileInputStream입력 스트림바이트 단위기반 스트림
FileReader입력 스트림문자 단위기반 스트림
BufferedInputStream입력 스트림바이트 단위보조 스트림
BufferedReader입력 스트림문자 단위보조 스트림
FileOutputStream출력 스트림바이트 단위기반 스트림
FileWriter출력 스트림문자 단위기반 스트림
BufferedOutputStream출력 스트림바이트 단위보조 스트림
BufferedWriter출력 스트림문자 단위보조 스트림

💡 문자 기반 스트림?

자바의 Char 형은 2 byte를 사용하는데, 바이트 단위는 입출력 단위가 1 byte이므로 문자를 처리하기 어려웠다. 이를 보완하기 위해 등장한 것이 바로 문자 기반 스트림이다.

💡 보조 스트림이란?

기반 스트림은 대상에 직접 자료를 읽고 쓰는 기능의 스트림이라면, 보조 스트림은 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있는 스트림을 의미한다. 오로지 보완하는 용도이기에 실제 데이터를 주고 받지 않고, 데이터를 입출력하는 기능은 없다. 따라서 스트림을 먼저 생성한 후 보조 스트림을 생성하여 사용한다.

대표적으로 보조 스트림에 해당하는 'Buffered'로 시작하는 스트림들이 있다. 이 스트림들은 버퍼를 이용한 입출력 성능 향상에 사용되는 - 말 그대로 보조용 스트림이다.

그 중에서도 내가 코딩 테스트에서 입력값을 받아오는데 사용할 내용만 더 정리해보자. 'Buffered'로 시작하는 보조 스트림을 이용하여 효율적으로 문자를 읽고 쓰기 위한 버퍼링 기능을 사용해보자.

만약, FileReader, InputStreamReader와 같은 스트림만을 사용하면 시스템은 바이트별로 사용자의 입력을 받아서 처리하는 동작을 반복하며 많은 자원을 소모하게 될 것이다. 하지만 'Buffered' 보조 스트림을 사용할 경우, 시스템은 버퍼가 비어있을 때만 실제 IO를 일으켜 데이터를 읽어들이고 비어있지 않을 경우에는 메모리에 있는 버퍼의 데이터를 읽어 처리하므로써 더 효율적으로 자원을 소모할 수 있다.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.StringTokenizer;

public class Main {

    public static void main(String[] args) throws IOException {
        // 기본 버퍼 사이즈 사용 (생성자를 이용하여 버퍼 사이즈 지정 가능)
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        String str = br.readLine();
        StringTokenizer strTokenizer = new StringTokenizer(str);

        while(strTokenizer.hasMoreTokens()) {
            bw.write(strTokenizer.nextToken());
            bw.newLine();
        }
		bw.flush();
        bw.close();
        br.close();
    }
}

💡 InputStreamReader, OutputStreamWriter 란?

이 두 개 보조 스트림은 바이트 기반 스트림을 문자 기반 스트림으로 연결시켜주는 역할을 수행한다. 바이트 기반 스트림의 데이터를 지정된 인코딩 문자 데이터로 변환하는 작업도 처리해준다.

예를 들어, 중국어로 작성된 파일을 읽을 때 InputStreamReader로 인코딩이 중국어로 되어있다는 것을 지정해주어야 파일이 깨지지 않고, OutputStreamWriter로 파일에 텍스트를 저장할 때 인코딩을 지정하지 않으면 OS가 자체적으로 사용하는 인코딩으로 데이터를 저장한다.

💡 System.in (Standard Input Stream)란?

한 바이트씩 읽어 들이는데 사용한다. 한글과 같은 여러 바이트로 된 문자를 읽기 위해서는 보조 스트림이 필요하다.

💡 StringTokenizer란?

StringTokenizer는 지정한 구분자로 문자열을 나누어주는 클래스다. 구분자로 나뉘어진 문자열 요소들을 토큰이라고 한다. String의 split() 메서드를 이용한 것과 동일한 결과를 만들어내지만, 정규식 기반으로 자르는 메서드이기에 내부구조가 복잡하기 때문에 단순히 공백 자리를 땡겨 채우는 StringTokenizer의 속도가 훨씬 빠르다. (기본 구분자는 공백이다!)

번외로, 여기서 사용한 IO와는 다르게 NIO도 존재한다. 이 두 개의 대표적인 차이는 Blocking이냐 Non-Blocking이냐의 차이다. 응답 속도의 차이가 있으나, 코딩 테스트에서는 기본 IO를 이용해도 무방하다. 하지만 면접에서도 나올 수 있는 질문이니 이전에 정리한 IO가 느린 이유가 뭘까? NIO는 뭘까?를 다시 읽어봐야겠다. 🫠

Scanner

우리가 흔히 사용하는 방법이 Scanner를 이용한 입력이다. Scanner를 사용하면 입력받은 데이터를 더 쉽게 가공할 수 있다는 장점을 가지고 있다. (정수와 실수 등의 기본적인 데이터 타입 입력을 받기 위한 클래스)

import java.util.Scanner;

public class Main {
	public static void main(String[] args) throws IOException {
    	Scanner scanner = new Scanner(System.in);
        
        // int number = scanner.nextInt();
        // System.out.println(number);
        
        while(scanner.hasNextLine()) {
        	// String str = scanner.nextLine();
        	String str = scanner.next();
            System.out.println(str);
        }
        
        scanner.close();
    }
}

그래서 뭘 이용해야하는가?

결론부터 말하자면 Scanner를 이용하자. 많은 양의 데이터를 입력받는 경우에는 BufferedReader 보조 스트림을 이용하여 받는 편이 효율적이지만, 코딩 테스트의 입력값들을 생각해보면 많은 양의 데이터를 받지 않기 때문이다. (Scanner와 BufferedReader의 버퍼 사이즈는 각각 1KB, 8KB)

BufferedWriter 보조 스트림도 마찬가지로 많은 양의 출력에서 사용하면 좋지만, 적은 양의 출력일 경우 System.out.println과 성능 차이가 미미하기에 코딩 테스트에서 System.out.println를 사용해도 큰 문제가 없다.

내가 말하는 문제들은 대부분의 경우를 의미한다. 따라서 무조건적으로 Scanner를 사용하자는 말은 아니라는 점을 기억하자!

이 글의 레퍼런스

profile
세상을 현명하게 이끌어갈 나의 성장 일기 📓

0개의 댓글