DSA/코딩테스트

Java 코딩테스트 완벽 가이드 - Part 1: 입출력 최적화

readyoun 2025. 1. 22. 19:19

Java 코딩테스트 완벽 가이드 - Part 1: 입출력 최적화의 모든 것

 

코딩테스트에서 가장 먼저 마주치는 것이 바로 입출력이다. 특히 백준에서는 입출력 처리 방식에 따라 시간 초과가 날 수 있어 최적화된 입출력 방식을 아는 것이 중요하다. 이 글에서는 Java로 코딩테스트를 준비하는 분들을 위해 입출력 처리 방법을 상세히 설명한다.

1. Scanner vs BufferedReader

많은 초보자들이 Scanner를 사용하지만, 대부분의 경우 BufferedReader를 사용하는 것이 좋다. 그 이유를 살펴보자.

BufferedReader의 장점

  1. 버퍼 사용으로 인한 속도 향상
    1. Scanner는 내부적으로 정규표현식을 사용하여 문자열을 파싱한다
    2. BufferedReader는 버퍼에 일정량의 데이터를 모아서 한 번에 처리한다
    3. 대량의 데이터 처리 시 최대 5배까지 성능 차이가 난다
  2. 메모리 사용량 감소
    1. Scanner는 입력 데이터를 다양한 타입으로 파싱하기 위한 부가적인 객체들을 생성한다
    2. BufferedReader는 단순히 문자열로만 읽어들여 메모리 사용이 효율적이다

2. 기본 입출력 템플릿

다음은 코딩테스트에서 바로 사용할 수 있는 표준 템플릿이다:

import java.io.*;
import java.util.*;

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 line = br.readLine();

        // 공백으로 구분된 정수들 읽기
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());

        // 결과 출력
        bw.write(String.valueOf(n + m));
        bw.newLine();

        // 버퍼 비우기
        bw.flush();
        br.close();
        bw.close();
    }
}

3. 자주 사용되는 입력 패턴

3.1 정수 배열 입력받기

// N개의 정수를 배열로 입력받기
int N = Integer.parseInt(br.readLine());
int[] arr = new int[N];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i < N; i++) {
    arr[i] = Integer.parseInt(st.nextToken());
}

3.2 2차원 배열 입력받기

// N x M 크기의 2차원 배열 입력받기
int N = Integer.parseInt(br.readLine());
int M = Integer.parseInt(br.readLine());
int[][] arr = new int[N][M];

for (int i = 0; i < N; i++) {
    StringTokenizer st = new StringTokenizer(br.readLine());
    for (int j = 0; j < M; j++) {
        arr[i][j] = Integer.parseInt(st.nextToken());
    }
}

4. 주의사항 및 팁

  1. IOException 처리
    1. main 메소드에 throws IOException을 반드시 추가한다
    2. try-catch문을 사용할 경우 코드가 길어지므로 권장하지 않는다
  2. BufferedWriter 사용 시 주의점
    1. write() 메소드는 String 타입만 받는다
    2. 숫자를 출력할 때는 String.valueOf()를 사용한다
    3. 마지막에 반드시 flush()를 호출해야 한다
  3. 메모리 관리
    1. 입력이 많은 경우 StringBuilder를 활용하여 문자열을 한 번에 출력한다
    2. 더 이상 사용하지 않는 객체는 null 처리하여 GC를 돕는다

Java 코딩테스트 필수 입출력 - 더 쉽게, 더 깔끔하게

코딩테스트를 준비하다 보면 입출력 코드가 너무 길어 실제 문제 해결 로직에 집중하기 어려울 때가 있다. 상황별로 가장 간단하게 사용할 수 있는 입출력 방법을 유형별로 분류했다.

1. 기본 템플릿 - 가장 간단한 버전

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();

        // 여기에 문제 해결 로직 작성

        System.out.println(sb);
    }
}

 

이 템플릿의 장점:

  1. BufferedWriter 없이 StringBuilder로 출력 관리
  2. close() 호출 불필요
  3. flush() 호출 불필요
  4. 코드가 짧고 깔끔함

2. 입력 패턴별 최적화 코드

2.1 한 줄에 여러 정수 입력

// 방법 1: StringTokenizer 사용
StringTokenizer st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());

// 방법 2: split() 사용
String[] input = br.readLine().split(" ");
int N = Integer.parseInt(input[0]);
int M = Integer.parseInt(input[1]);

2.2 여러 줄의 입력 처리

// 입력 개수가 주어진 경우
int T = Integer.parseInt(br.readLine());
for(int t = 0; t < T; t++) {
    String line = br.readLine();
    sb.append(line).append('\n');
}

// 입력 개수가 정해지지 않은 경우
String line;
while((line = br.readLine()) != null) {
    sb.append(line).append('\n');
}

3. 상황별 추천 방식

1. 단순 출력이 많은 경우

StringBuilder sb = new StringBuilder();
// ... 로직 ...
sb.append(결과).append('\n');
System.out.println(sb);


2. 정수 계산 결과만 출력하는 경우

System.out.println(결과);

 

3. 테스트 케이스가 많은 경우

int T = Integer.parseInt(br.readLine());
StringBuilder sb = new StringBuilder();
for(int t = 0; t < T; t++) {
    // ... 로직 ...
    sb.append(결과).append('\n');
}
System.out.println(sb);

4. 결론 및 추천사항

  1. 대부분의 경우 StringBuilder + System.out.println조합이면 충분하다.
  2. 입력은 BufferedReader를 기본으로 사용한다.
  3. 특별한 이유가 없다면 close()나 flush()는 생략 가능하다.
  4. 극단적인 성능이 필요한 경우에만 BufferedWriter를 고려한다.

이렇게 간단한 버전의 입출력 템플릿을 사용하면 코드가 더 깔끔해지고, 실제 문제 해결에 더 집중할 수 있다.

마무리

입출력은 모든 코딩테스트 문제의 기본이다. BufferedReader와 BufferedWriter를 사용한 최적화된 입출력 처리는 실행 시간을 크게 단축시킬 수 있다.

참고자료