- 3줄 요약 (Summary)
- 수도꼭지에서 물방울이 떨어질때마다 컵에 바로바로 담는 방식
- 장점:
nextInt(),next()등 메서드가 직관적이라 사용하기 매우 편리함.- 단점: 입력마다 문자를 해석(Parsing)하고 정규식을 검사하는 과정 때문에 매우 느림. (대용량 데이터 처리에 부적합)
상세 분석: 🔍 왜 Scanner는 느릴까?
Scanner는 단순한 입력 도구가 아닌 편의성 중심의 파싱 도구로 설계되었다.
- 3줄 요약 (Summary)
- 수도꼭지에서 물을 큰 양동이(Buffer)에 가득 받아두고, 필요할 때마다 퍼서 쓰는 방식
- 장점: 데이터를 덩어리(Chunk) 채로 가져오므로 입출력 횟수가 줄어 Scanner보다 약 10배 이상 빠름
- 단점: 무조건
String으로만 읽어오므로, 데이터를 가공(형변환 등)하려면 추가적인 코드가 필요함
상세 분석: 🔍 왜 BufferedReader는 빠를까?
BufferedReader는 성능 중심의 버퍼링 도구이다. 하드웨어적인 입출력 효율을 극대화한다.
nextToken()을 호출할 때마다 현재 위치(Cursor)에서 다음 구분자까지만 탐색하여 반환한다. 이로 인해 초기 메모리 할당 비용이 거의 없다.java.util.regex.Pattern 객체를 생성하고 컴파일하는 과정이 포함되어 있어 초기 구동 비용이 발생한다.String[]배열 객체로 한 번에 생성한다. 잘린 문자열이 많을 수록 힙 메모리 (Heap Memory) 사용량이 순간적으로 급증할 수 있다.10만 개의 정수가 한 줄에 하나씩 입력되는 상황
별도의 예외 처리가 필요 없고, nextInt()로 바로 정수를 가져올 수 있어 코드가 간결하다.
import java.util.*;
public class ScannerExample {
public static void main(String[] args) {
// 내부 버퍼가 작고 (1KB), 입력마다 정규식 검사를 수행함
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 정수 파싱을 자동으로 수행
for (int i = 0; i < n; i++) {
int value = sc.nextInt(); // 편리하지만 느림
// 로직 처리 ...
}
}
}
데이터를 한 번에 문자열(String)로 가져온다. 반드시 형변환(Integer.parseInt)이 필요하며, 예외 처리(throws IOException)를 명시해야 한다.
import java.io.*;
public class BufferedExample {
public static void main(String[] args) throws IOException { // 1. 예외 처리 필수
// 8KB 버퍼를 사용하여 덩어리째 읽어옴
BufferedReader br = new BufferedReader(new InputStraemReader(System.in));
// 2. 라인 단위로 읽어서 직접 형변환 수행 (Raw String -> int)
int n = Integer.parseInt(br.readLine());
for (int i = 0; i < n; i++) {
int value = Integer.parseInt(br.readLine()); // 빠름
// 로직 처리...
}
}
}
공백으로 구분된 긴 문장려 10 20 30 ...를 잘라서 처리하는 상황
코드는 한 줄로 끝나지만, 내부적으로 정규식 컴파일과 뱅러 전체 할당이 일어나 메모리와 시간을 많이 쓴다.
String line = "10 20 30 40 50";
// 정규식 (" ")을 사용하며, 결과를 String[] 배열 객체로 한 번에 생성함
String[] value = line.split(" ");
for(String s : value) {
int value = Integer.parseInt(s);
// 로직 처리...
}
정규식을 사용하지 않고 단순히 문자를 훑으며 자른다.
배열을 미리 만들지 않아 메모리 효율이 매우 좋다.
import java.util.StringTokenizer;
String line = "10 20 30 40 50";
// 1. 객체 생성 시 문자열 전달 (기본 구분자는 공백)
StringTokenier st = new StringTokenizer(line);
// 2. 이터레이터 패턴: 다음 토큰이 있는지 확인하며 하나씩 꺼냄
while (st.hasMoreToken()) {
// 꺼낼 때만 해당 위치의 문자열을 생성하므로 메모리 할당 비용이 낮음
int value = Integer.parseInt(st.nextToken());
// 로직 처리...
}
public class FastIO {
public static void main(String[] args) throws IOException {
// 1. 입력 가속: BufferedReader 사용
BufferedREader br = new BufferedREader(new InputStreamReader(System.in));
// 2. 출력 가속: 매번 출력하지 않고 StringBuilder에 모았다가 한 번에 출력
StringBuilder sb = new StringBuilder();
// 첫 줄에 데이터 개수 N이 들어온다고 가정
int n = Integer.parseInt(br.readLine());
// 3. 문자열 분리: StringTokenizer 사용 (split 보다 훨씬 빠름)
// br.readLine()으로 한 줄을 통째로 읽어와 토크나이저에 전달
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
// st.nextToken()으로 문자열을 하나씩 꺼내고 형변환
int value = Integer.parseInt(st.nextToken());
// 결과값을 StringBuilder에 추가 (\n은 줄바꿈)
sb.append(value * 2).append("\n");
}
// 4. 최종 결과 한 번에 출력 (시스템 콜 최소화)
System.out.println(sb.toString());
}
}
| 기술 도구 | 추천 사용처 | 핵심 장점 (Key Point) |
|---|---|---|
| Scanner | 데이터 양이 적고 빠른 구현이 필요할 때 | nextInt() 등 타입별 자동 파싱 지원으로 사용이 매우 편리함 |
| BufferedReader | 알고리즘 문제, 대용량 파일 읽기 | 8KB 버퍼 활용 및 시스템 콜 최소화로 압도적인 입력 속도 |
| String.split() | 복잡한 구분자(정규식) 처리가 필요할 때 | 코드가 간결하며 정규 표현식을 활용한 유연한 분리 가능 |
| StringTokenizer | 공백 기준의 대량 문자열 분리 | 정규식 미사용 및 커서 기반 탐색으로 메모리 및 CPU 효율 극대화 |
| System.out.println | 단발성 출력, 디버깅 용도 | 사용법이 가장 직관적이나 호출 시마다 I/O가 발생하여 속도가 느림 |
| StringBuilder | 반복문 내 연속적인 출력 발생 시 | 내부 버퍼에 문자열을 모아 단 한 번의 출력으로 처리 (속도 가속) |