1. Java에서 데이터를 입력받을 때 사용하는 'System.in' 이게 뭘까?
System.in은 "표준 입력 스트림"을 의미
- "표준 입력"이란?
- 보통 키보드에서 사용자가 입력하는 데이터
- "스트림(Stream)"이란?
- 데이터가 한 방향으로 흘러가는 통로를 의미
즉, System.in은 키보드로 입력한 데이터를 '한 방향'으로 읽을 수 있게 하는 통로
🧠 쉽게 비유해보자
System.in = 수도관에 흐르는 물줄기
- 물줄기는 계속 한 방향으로 흐른다. (거슬러 올라갈 수 없음)
- 한 번 지나간 물은 다시 읽을 수 없다.
- 물줄기에 컵(BufferedReader, Scanner 등)을 대서 물(데이터)을 받아오는 것.
📚 System.in의 구조
- 타입: InputStream
- 데이터 단위: Byte(바이트)
- 방향: 읽기 전용(Forward Only)
InputStream input = System.in;
- System.in은 데이터를 바이트 단위로 읽기 때문에, 사람이 읽기 좋은 형태(문자열)로 만들려면 추가 작업이 필요
그래서 보통은 이렇게 사용함
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
2. System.in 선언과 데이터 읽기 타이밍
🚨 중요한 오해 바로잡기
System.in을 선언하거나 BufferedReader를 만든 순간에는 입력 데이터를 읽지 않음
- System.in이나 BufferedReader를 "선언"하는 건 "입력 스트림을 사용할 준비만 하는 것"
- 실제로 입력 데이터가 프로그램 안으로 들어오는 시점은 read(), readLine() 같은 메서드를 호출할 때
수도꼭지 앞에 컵을 들고 있는 것과 같다.
(아직 물은 안 받음 → readLine()을 해야 물을 받는다.)
📋 흐름 정리
(1) System.in 생성 → 스트림 준비만 함
(2) BufferedReader 생성 → System.in을 감쌈 (여전히 읽지 않음)
(3) readLine() 호출 → 이때 진짜로 데이터 읽음
🤔 구조적으로는 어떤 흐름일까?
[키보드 입력] → [운영체제(OS) 메모리 버퍼] → [System.in 내부 버퍼] → [BufferedReader.readLine()]
- 사용자가 키보드로 입력
- 운영체제가 입력을 메모리에 저장
- System.in이 그 메모리 버퍼로부터 데이터를 가져옴
- BufferedReader가 System.in을 통해 데이터를 읽고, 한 줄 단위로 반환
🎯 디테일
- System.in은 프로그램 시작할 때 딱 하나만 생성된다.
- 새로 BufferedReader를 만들더라도, System.in은 변하지 않는다.
- **커서(cursor)**가 한 번 이동하면,
이미 읽은 데이터는 다시 읽을 수 없다. - 남은 데이터가 없으면,
- readLine()은 null을 반환하고,
- read()는 -1을 반환한다.
3. System.in은 한 번만 사용해야 하는가?
결론부터 말하면 경우에 따라서는 System.in을 여러 번 감싸서 사용해도 (예: BufferedReader 여러 번 만들기) 시스템이 정상 동작할 수 있다. 하지만 System.in은 한 번만 감싸서 사용하는 것이 가장 안전하고 좋은 습관이다.
1. 경우에 따라 System.in을 여러 번 사용해도 "정상 동작"할 수 있다
- 예를 들어, 남은 데이터가 충분히 있을 때
- 또는 스트림 커서가 이동했더라도 사용자가 새 입력을 넣어줄 때
- 프로그램은 멈추지 않고 정상적으로 readLine(), read()를 이어서 수행할 수 있다.
남은 입력이 있거나 새 입력이 들어오면 새로 만든 BufferedReader도 정상적으로 작동할 수 있다.
2. 하지만 "원칙적으로는" 한 번만 사용하는 것이 맞다
- System.in은 스트림 하나로만 살아있는 구조다.
- 여러 객체(BufferedReader, Scanner 등)를 만들어도
- 다 같은 System.in을 공유한다.
- 스트림은 읽을 때마다 커서가 이동하고,
다시 돌아갈 수 없기 때문에 - 여러 번 선언하면 어디까지 읽었는지 관리가 어려워진다.
- 결국 프로그램이 커서 관리에 실패하면
- 예상치 못한 null, -1, blocking(입력 대기) 문제가 터질 수 있다.
3. 또한 여러번 읽기 객체(BufferedReader 등)를 만들고 동시에 사용하면 버그가 발생할 수 있다.
- 중복 읽기, 누락, null 반환, blocking 등의 문제
예시 코드로 알아 보자
임의로 작성한 코드와 테스트케이스
import java.io.*;
import java.util.*;
public class Main {
static int N, M;
static HashSet<String> set = new HashSet<>();
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
init();
for (int i = 0; i < M; i++) {
String[] arr = br.readLine().split(",");
countWord(arr);
}
}
public static void countWord(String[] arr) {
for(String s : arr) set.remove(s);
System.out.println(set.size());
}
public static void init() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
for (int i = 0; i < N; i++) set.add(br.readLine());
}
}
5 2
map
set
dijkstra
floyd
os
map,dijkstra
map,floyd
🔹 STEP 0: 프로그램 시작
- OS는 대기 중, 입력을 사용자에게 받음
- 사용자가 전체 입력을 한꺼번에 붙여넣기
- OS는 입력을 한 줄씩 stdin 버퍼에 저장
- 자바는 System.in을 통해 이 스트림을 바라봄
[OS stdin 버퍼]
→ 5 2\n
→ map\n
→ set\n
→ dijkstra\n
→ floyd\n
→ os\n
→ map,dijkstra\n
→ map,floyd\n
🔹 STEP 1: main 함수에서 BufferedReader br1 생성
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- System.in은 여전히 같은 stdin
- br1이 생성될 때 내부에 char[] buffer를 가짐 (기본 8192자)
- 이 시점에는 아직 readLine() 호출 안 했기 때문에 아무 것도 읽지 않음
🔹 STEP 2: init() 호출 → BufferedReader br2 생성
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- 새로운 BufferedReader (br2)가 만들어짐
- 하지만 System.in은 여전히 같은 스트림
- 이제 br2.readLine()을 호출하면서 문제가 시작됨
🔹 STEP 3: init()에서 br2.readLine() 호출
StringTokenizer st = new StringTokenizer(br.readLine()); // br2
- 이때 BufferedReader.readLine()은 내부적으로
- System.in.read()를 호출해서
- OS stdin에서 여러 바이트를 한꺼번에 읽고
- char[] buffer에 저장함
- 문제는 BufferedReader는 보통 8192자까지 미리 읽는다는 것
- 그래서 이 시점에 br2는 "5 2"뿐만 아니라
"map\nset\ndijkstra\n..."까지 미리 읽어버릴 수 있음
🔹 STEP 4: init()에서 N개 키워드 읽기
for (int i = 0; i < N; i++) set.add(br.readLine());
- br2는 이미 대부분의 데이터를 내부 버퍼에 담았기 때문에,
- "map" ~ "os"까지 한 줄씩 소비
- 남은 줄은:
map,dijkstra\n map,floyd\n
→ 아직 System.in에는 존재할 수 있지만, 버퍼 밖에 있음
🔹 STEP 5: main 함수로 돌아감 → br1.readLine() 호출
String[] arr = br.readLine().split(","); // br1
- 문제는 br1은 이제 막 첫 readLine()을 호출하는데,
- System.in은 이미 상당량을 br2가 읽어가버렸음
- br1의 내부 버퍼는 비어 있음
- 남은 입력이 없거나, 남아 있더라도 System.in.read()가 끝에 도달하면 → null 또는 빈 문자열 반환
'cs > Java' 카테고리의 다른 글
[Java] try-finally보단 try-with-resources? (0) | 2025.05.09 |
---|