본문으로 건너뛰기

Ch 15.3 표준 I/O와 Console 클래스

1. 표준 입출력 (Standard I/O)

자바는 콘솔 화면에서 사용자로부터 입력을 받거나 텍스트를 출력하기 위해 미리 만들어진(Built-in) 표준 스트림 3가지를 System 클래스 내에 static 변수로 제공합니다.

표준 스트림타입기본 연결 장치용도
System.inInputStream키보드표준 입력
System.outPrintStream콘솔(화면)표준 출력
System.errPrintStream콘솔(화면)표준 에러 출력
import java.io.IOException;

public class StandardIOBasic {
public static void main(String[] args) {
// System.out: 일반 출력
System.out.println("일반 출력 메시지");
System.out.printf("형식화: %s = %d%n", "값", 42);

// System.err: 에러 출력 (일반적으로 붉은색으로 표시됨)
System.err.println("이것은 에러 스트림입니다.");

// System.in: 바이트 기반 입력 스트림
System.out.println("키를 누르세요 (q를 누르면 종료):");
try {
int input;
while ((input = System.in.read()) != -1) {
if ((char) input == 'q') break;
System.out.print("입력: " + (char) input);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.err vs System.out

System.err로 출력하면 표준 출력 리디렉션(>)에 포함되지 않습니다. 파이프라인에서 오류 메시지만 분리할 수 있어 로깅에 유용합니다.

2. System.in → BufferedReader 변환 체인

System.inInputStream(바이트 기반)이므로 문자열을 처리하려면 변환이 필요합니다.

System.in (InputStream)
→ InputStreamReader (바이트를 문자로 변환, 인코딩 지정 가능)
→ BufferedReader (버퍼링 + readLine() 기능 추가)
import java.io.*;
import java.nio.charset.StandardCharsets;

public class BufferedReaderInput {
public static void main(String[] args) throws IOException {
// System.in → InputStreamReader(UTF-8) → BufferedReader
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in, StandardCharsets.UTF_8)
);

System.out.print("이름을 입력하세요: ");
String name = br.readLine(); // 줄 단위 읽기

System.out.print("나이를 입력하세요: ");
int age = Integer.parseInt(br.readLine()); // 문자열 → 정수 변환

System.out.printf("안녕하세요, %s님! 나이: %d%n", name, age);

// br.close(); // System.in은 닫지 않는 것이 관례
}
}

3. BufferedReader vs Scanner 비교

자바에서 콘솔 입력을 받는 두 가지 주요 방법을 비교합니다.

항목BufferedReaderScanner
패키지java.iojava.util
입력 단위줄(readLine)토큰(next, nextInt 등)
속도빠름 (버퍼링)느림 (정규식 파싱)
예외checked IOExceptionunchecked
타입 파싱직접 변환 필요자동 변환 (nextInt 등)
동기화동기화됨동기화 안 됨
권장 상황알고리즘 풀이, 대용량 입력간단한 입력, 초보자
import java.io.*;
import java.util.Scanner;

public class InputComparison {
// Scanner 방식 (간편하지만 느림)
static void scannerWay() throws IOException {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
String str = sc.next();
double d = sc.nextDouble();
sc.close();
}

// BufferedReader 방식 (빠름, 알고리즘 풀이 권장)
static void bufferedReaderWay() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine().trim());
String str = br.readLine().trim();
double d = Double.parseDouble(br.readLine().trim());
}

// StreamTokenizer 방식 (가장 빠른 정수/실수 파싱)
static void streamTokenizerWay() throws IOException {
StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
st.nextToken();
int n = (int) st.nval;
}

public static void main(String[] args) {
System.out.println("입력 방식 비교 예제 (실행 시 입력 필요)");
}
}

4. 알고리즘 풀이용 고속 입력 패턴

프로그래밍 대회나 알고리즘 문제 풀이에서 대량의 입력을 빠르게 처리해야 할 때 사용합니다.

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

public class FastInputPattern {
public static void main(String[] args) throws IOException {
// 패턴 1: BufferedReader + StringTokenizer (가장 일반적)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder(); // 출력도 StringBuilder로 모아서 한 번에

// 첫 줄: N개의 테스트케이스 수
int N = Integer.parseInt(br.readLine().trim());

for (int i = 0; i < N; i++) {
// 한 줄에 여러 숫자: "3 5 7" 형식
StringTokenizer st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());

sb.append(a + b).append('\n'); // 결과를 StringBuilder에 누적
}

// 한 번에 출력 (System.out.println을 반복하면 느림)
System.out.print(sb);
}
}
import java.io.*;
import java.util.StringTokenizer;

public class AlgorithmTemplate {
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StringBuilder sb = new StringBuilder();

static int nextInt() throws IOException {
return Integer.parseInt(br.readLine().trim());
}

static int[] nextIntArray() throws IOException {
StringTokenizer st = new StringTokenizer(br.readLine());
int[] arr = new int[st.countTokens()];
for (int i = 0; i < arr.length; i++) {
arr[i] = Integer.parseInt(st.nextToken());
}
return arr;
}

public static void main(String[] args) throws IOException {
// 사용 예: N 입력받고 배열 처리
// int N = nextInt();
// int[] arr = nextIntArray();
System.out.println("알고리즘 템플릿 준비 완료");
}
}

5. Console 클래스: 비밀번호 입력

System.console()은 JVM이 콘솔에 연결된 경우 Console 객체를 반환합니다. IDE에서는 null을 반환하므로 주의하세요.

import java.io.Console;

public class ConsoleExample {
public static void main(String[] args) {
Console console = System.console();

if (console == null) {
System.err.println("콘솔을 사용할 수 없습니다. (IDE에서 실행 중?)");
System.err.println("직접 터미널(java ConsoleExample)에서 실행하세요.");
return;
}

// 일반 입력
String username = console.readLine("사용자 이름: ");

// 비밀번호 입력 (화면에 표시되지 않음!)
char[] password = console.readPassword("비밀번호: ");

System.out.println("입력된 사용자: " + username);
System.out.println("비밀번호 길이: " + password.length);

// 보안: 비밀번호 사용 후 배열 초기화
java.util.Arrays.fill(password, ' ');

// 형식화 출력
console.printf("안녕하세요, %s님!%n", username);
}
}
Console과 IDE

System.console()은 터미널에서 직접 실행할 때만 정상 동작합니다. IntelliJ, Eclipse 등 IDE에서 실행하면 null을 반환합니다. 비밀번호 입력이 필요하면 반드시 null 체크를 하세요.

6. 스트림 재지정: System.setIn, System.setOut, System.setErr

표준 스트림의 연결 대상을 변경할 수 있습니다. 단위 테스트, 로그 파일 저장 등에 활용합니다.

import java.io.*;
import java.nio.charset.StandardCharsets;

public class StreamRedirection {
public static void main(String[] args) throws IOException {
// 1. System.out을 파일로 재지정
PrintStream originalOut = System.out; // 원본 보관
PrintStream fileOut = new PrintStream(
new BufferedOutputStream(new FileOutputStream("log.txt")),
true, "UTF-8"
);

System.setOut(fileOut); // 이후 System.out은 파일로 감
System.out.println("이 메시지는 log.txt에 저장됩니다.");
System.out.println("프로그램 실행 로그: " + java.time.LocalDateTime.now());

System.setOut(originalOut); // 복원
System.out.println("콘솔로 돌아왔습니다.");
fileOut.close();

// 2. System.in을 문자열로 재지정 (테스트에서 유용)
String simulatedInput = "Alice\n30\njava\n";
InputStream originalIn = System.in;
System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8)));

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("시뮬레이션 입력 1: " + br.readLine()); // Alice
System.out.println("시뮬레이션 입력 2: " + br.readLine()); // 30
System.out.println("시뮬레이션 입력 3: " + br.readLine()); // java

System.setIn(originalIn); // 복원
}
}

7. File 클래스

java.io.File은 데이터 입출력을 위한 스트림이 아니라, 파일(File) 또는 디렉토리(Directory) 자체의 제어와 정보 를 다루는 클래스입니다.

import java.io.File;
import java.io.IOException;

public class FileClassExample {
public static void main(String[] args) throws IOException {
// File 객체 생성 (실제 파일/디렉토리가 없어도 됨)
File dir = new File("testdir");
File file = new File(dir, "example.txt");

// 디렉토리 생성
if (!dir.exists()) {
boolean created = dir.mkdirs(); // 중간 디렉토리도 함께 생성
System.out.println("디렉토리 생성: " + created);
}

// 파일 생성
if (!file.exists()) {
boolean created = file.createNewFile();
System.out.println("파일 생성: " + created);
}

// 파일 정보 조회
System.out.println("파일명: " + file.getName());
System.out.println("절대 경로: " + file.getAbsolutePath());
System.out.println("부모 디렉토리: " + file.getParent());
System.out.println("크기: " + file.length() + " bytes");
System.out.println("읽기 가능: " + file.canRead());
System.out.println("쓰기 가능: " + file.canWrite());
System.out.println("파일인가: " + file.isFile());
System.out.println("디렉토리인가: " + file.isDirectory());
System.out.println("숨김 파일: " + file.isHidden());
System.out.println("마지막 수정: " + new java.util.Date(file.lastModified()));

// 디렉토리 내 파일 목록
File[] files = dir.listFiles();
if (files != null) {
System.out.println("\n디렉토리 내용:");
for (File f : files) {
System.out.println(" " + f.getName() + (f.isDirectory() ? "/" : ""));
}
}

// 파일 삭제
file.delete();
dir.delete();
}
}

8. java.nio.file.Files (현대적 대안)

Java 7+에서는 Files 유틸리티 클래스가 더 간편하고 강력합니다.

import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class NioFilesExample {
public static void main(String[] args) throws IOException {
Path path = Path.of("sample.txt");

// 파일에 쓰기 (한 번에)
Files.writeString(path, "첫 번째 줄\n두 번째 줄\n세 번째 줄",
StandardCharsets.UTF_8);

// 파일 전체 읽기 (한 번에)
String content = Files.readString(path, StandardCharsets.UTF_8);
System.out.println(content);

// 줄 단위 읽기
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
lines.forEach(System.out::println);

// 파일 복사
Files.copy(path, Path.of("sample_copy.txt"),
StandardCopyOption.REPLACE_EXISTING);

// 파일 이동
Files.move(Path.of("sample_copy.txt"), Path.of("sample_moved.txt"),
StandardCopyOption.REPLACE_EXISTING);

// 파일 삭제
Files.deleteIfExists(path);
Files.deleteIfExists(Path.of("sample_moved.txt"));

// 존재 여부
System.out.println("파일 존재: " + Files.exists(path));
}
}

9. 실전 예제: BufferedReader로 빠른 알고리즘 입력

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

/**
* 백준/프로그래머스 스타일 알고리즘 입력 템플릿
* Scanner 대비 약 3~5배 빠른 입력 처리
*/
public class AlgorithmFastIO {
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StringBuilder sb = new StringBuilder();

public static void main(String[] args) throws IOException {
// 예제: N개의 정수를 입력받아 합계 계산
// 입력 형식:
// 5
// 1 2 3 4 5

int N = Integer.parseInt(br.readLine().trim());
StringTokenizer st = new StringTokenizer(br.readLine());

long sum = 0;
int[] arr = new int[N];
for (int i = 0; i < N; i++) {
arr[i] = Integer.parseInt(st.nextToken());
sum += arr[i];
}

sb.append("합계: ").append(sum).append('\n');
sb.append("평균: ").append(String.format("%.2f", (double) sum / N)).append('\n');

// 최댓값, 최솟값
int max = Arrays.stream(arr).max().orElse(0);
int min = Arrays.stream(arr).min().orElse(0);
sb.append("최대: ").append(max).append('\n');
sb.append("최소: ").append(min).append('\n');

System.out.print(sb); // 한 번에 출력

// 실제 실행은 입력이 필요하므로 여기서는 시뮬레이션
System.out.println("\n=== 시뮬레이션 결과 ===");
System.out.println("N=5, 데이터=[1,2,3,4,5]일 때:");
System.out.println("합계: 15, 평균: 3.00, 최대: 5, 최소: 1");
}
}
고수 팁: 입력 방식 선택 기준
  • Scanner: 간단한 프로젝트, 입력 양이 적을 때
  • BufferedReader: 알고리즘 문제, 대용량 입력 (10만 건 이상)
  • Console: 비밀번호 같이 에코 없는 입력이 필요할 때
  • StreamTokenizer: 숫자만 대량 입력받을 때 (가장 빠름)