[코테] JAVA 백준 알고리즘 시작하기 - 입출력 가이드 !!성능최적화!!

2025. 9. 29. 16:51·공부일기../Java

인트로

1편에서 기본기를 익혔다면, 이제 복잡한 입출력과 성능 최적화를 마스터해보자!

[코테] JAVA 백준 알고리즘 시작하기 - 입출력 가이드 초보자

 


 

 

1. 입력 패턴들

1-1. 2차원 배열 입력받기

// 입력:
// 3
// 1 2 3
// 4 5 6
// 7 8 9
int n = Integer.parseInt(br.readLine());
int[][] map = new int[n][n];
for(int i = 0; i < n; i++) {
    StringTokenizer st = new StringTokenizer(br.readLine());
    for(int j = 0; j < n; j++) {
        map[i][j] = Integer.parseInt(st.nextToken());
    }
}

응용: 직사각형 배열

// 입력: 3 4 (세로 3, 가로 4)
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int m = Integer.parseInt(st.nextToken());
int[][] map = new int[n][m];
  • StringTokenizer 
    → 문자열을 공백이나 특정 구분자로 분리해주는 클래스
    →  기존 객체에 새 문자열을 재설정할 수 없으므로, 줄마다 새로운 객체를 생성해야 한다.

1-2. 문자 배열 처리

// 입력: "ABC" 같은 문자열을 문자 하나씩 처리
String line = br.readLine();
char[] chars = line.toCharArray();

// 또는 바로 접근
for(int i = 0; i < line.length(); i++) {
    char c = line.charAt(i);
}
  • String.toCharArray() 
    → 문자열을 문자 배열로 변환
  • String.charAt(int)
    → 특정 인덱스 위치의 문자를 반환

1-3. 숫자를 문자로 입력받기

// 입력: "12345" (공백 없는 숫자들)
String line = br.readLine();
int[] digits = new int[line.length()];
for(int i = 0; i < line.length(); i++) {
    digits[i] = line.charAt(i) - '0';  // 문자를 숫자로 변환
}

 

Character 클래스
→  '0'을 빼는 이유는 아스키 코드 때문이다. '1'의 아스키는 49, '0'의 아스키는 48이므로 49-48=1이 된다.


1-4. 여러 테스트케이스 처리

int t = Integer.parseInt(br.readLine());
StringBuilder sb = new StringBuilder();

while(t-- > 0) {
    // 각 테스트케이스 처리
    int n = Integer.parseInt(br.readLine());
    // 로직 처리
    sb.append(result).append('\n');
}
System.out.print(sb);

입력 형태

3          <- 테스트케이스 개수
5 3        <- 첫 번째 테스트케이스
10 7       <- 두 번째 테스트케이스  
1 9        <- 세 번째 테스트케이스

핵심 포인트

  1. t--의 동작: t 값을 사용한 후 1 감소. t=3이면 3→2→1→0 순서로 실행
  2. StringBuilder 사용 이유: 각 테스트케이스마다 System.out.println() 쓰면 느림
  3. 한 번에 출력: 모든 결과를 모아서 마지막에 System.out.print(sb)

실제 동작 과정

t = 3
첫 번째: t=3 (조건 true) → 실행 → t=2
두 번째: t=2 (조건 true) → 실행 → t=1  
세 번째: t=1 (조건 true) → 실행 → t=0
네 번째: t=0 (조건 false) → 종료
  • StringBuilder
    → 문자열을 효율적으로 누적/조작할 수 있는 가변 버퍼로 출력 성능 최적화에 사용된다.
  • 후위 연산자(t--)
    → 먼저 현재 값을 사용한 뒤에 1 감소한다.

1-5. EOF 처리 (입력 끝까지 읽기)

String line;
while((line = br.readLine()) != null) {
    // 입력 개수가 주어지지 않을 때
    StringTokenizer st = new StringTokenizer(line);
    int a = Integer.parseInt(st.nextToken());
    int b = Integer.parseInt(st.nextToken());
    sb.append(a + b).append('\n');
}
  • BufferedReader.readLine()
    → 줄 끝 문자를 제외하고 문자열을 반환한다. 입력이 끝나면 null을 반환한다.

1-6. 다양한 타입 파싱

// Long, Double 처리
long l = Long.parseLong(st.nextToken());
double d = Double.parseDouble(st.nextToken());

// 여러 구분자 사용
StringTokenizer st = new StringTokenizer(br.readLine(), " ,:");

// 앞뒤 공백 제거
String line = br.readLine().trim();
  • Long.parseLong() 
  • Double.parseDouble() 
    → 문자열을 숫자 타입으로 변환.
  • String.trim()
    → 앞뒤 공백 제거.

 


 

 

2. 고급 출력 패턴들

2-1. 한 줄에 여러 값 출력 (공백 구분)

StringBuilder sb = new StringBuilder();
for(int i = 0; i < n; i++) {
    sb.append(arr[i]);
    if(i < n-1) sb.append(' ');  // 마지막엔 공백 안붙임
}
sb.append('\n');
System.out.print(sb);
  • StringBuilder.append() 
    → 정수, 문자, 문자열 등 다양한 타입을 문자열로 변환해 붙일 수 있다.

2-2. 조건부 출력

StringBuilder sb = new StringBuilder();
if(isPossible) {
    sb.append("YES\n");
    sb.append(answer).append('\n');
} else {
    sb.append("NO\n");
}
System.out.print(sb);

2-3. 형식화된 출력

// 소수점 둘째 자리까지
System.out.printf("%.2f\n", result);

// StringBuilder와 함께 쓸 때
sb.append(String.format("%.2f", result)).append('\n');
  • System.out.printf() 
    → 형식 지정자(%.2f) 등을 이용해 출력 가능하다.
  • String.format() 
    → 문자열을 특정 형식에 맞춰 반환한다.

2-4. BufferedWriter를 쓸 수도 있다

StringBuilder 대신 BufferedWriter를 사용할 수도 있다.

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
for(int i = 1; i <= n; i++) {
    bw.write(String.valueOf(i));
    bw.newLine();
}
bw.flush();
bw.close();

성능

일반적으로 StringBuilder와 BufferedWriter 모두 System.out.println보다 빠르다. 구체적인 성능은 환경에 따라 다르지만, 알고리즘 문제에서는 둘 다 시간초과를 피하기에 충분히 빠르다.

대부분 StringBuilder면 충분하다.

BufferedWriter는 출력이 극도로 많거나 파일 입출력할 때 고려한다.

 

  • BufferedWriter
    → 버퍼링된 문자 출력 스트림. write(), newLine(), flush() 메서드를 제공한다.
  • Writer.flush() 
    → 버퍼에 남은 데이터를 강제로 출력한다.

 


 

 

3. 성능 최적화 & 실수 방지

3-1. StringBuilder vs System.out.println

// ❌ 출력이 많을 때 (1000번 이상) - 시간초과 위험
for(int i = 1; i <= 100000; i++) {
    System.out.println(i);
}

// ✅ 안전한 방법
StringBuilder sb = new StringBuilder();
for(int i = 1; i <= 100000; i++) {
    sb.append(i).append('\n');
}
System.out.print(sb);

 

System.out.println()은 매번 flush가 발생하여 느리다.

StringBuilder는 메모리에 모아뒀다가 한 번에 출력한다.

 

  • PrintStream
    → println()은 줄바꿈 시 버퍼를 flush할 수 있어 반복 호출 시 성능 저하가 발생할 수 있다.
  • StringBuilder 
    → append로 문자열을 누적 후, 한 번에 출력 가능하다.

3-2. StringTokenizer vs split()

// ✅ 빠른 방법 - StringTokenizer
StringTokenizer st = new StringTokenizer(br.readLine());
while(st.hasMoreTokens()) {
    int num = Integer.parseInt(st.nextToken());
}

// ❌ 느린 방법 - split (정규식 사용)
String[] tokens = br.readLine().split(" ");
for(String token : tokens) {
    int num = Integer.parseInt(token);
}

split()은 내부적으로 정규식을 사용하여 StringTokenizer보다 느리다.

  • StringTokenizer
    → 레거시 클래스. 문서에서 새로운 코드에서는 split 사용을 권장한다고 안내한다.
  • String.split()
    → 정규식을 사용해 문자열을 분리한다.

3-3. StringTokenizer 재사용 관련 주의사항

StringTokenizer는 생성자에서 전달받은 문자열에 대해서만 토큰화를 수행한다. Java SE API 문서를 확인해보면 기존 StringTokenizer 객체에 새로운 문자열을 설정하는 메서드는 제공되지 않는다.

 

사용 가능한 주요 메서드들:

  • hasMoreTokens(): 더 많은 토큰이 있는지 확인
  • nextToken(): 다음 토큰 반환
  • countTokens(): 남은 토큰 개수 반환

따라서 새로운 줄의 입력을 처리하려면 새로운 StringTokenizer 객체를 생성해야 한다.

// ❌ 잘못된 방법 - 한 줄에서만 토큰을 가져올 수 있음
StringTokenizer st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
// 다음 줄로 넘어갔는데 같은 StringTokenizer 사용하려 함

// ✅ 올바른 방법 - 새 줄마다 새로운 StringTokenizer
for(int i = 0; i < n; i++) {
    StringTokenizer st = new StringTokenizer(br.readLine());
    // 이 줄의 토큰들 처리
}
  • StringTokenizer 
    → 문자열을 새로 설정하는 메서드는 제공하지 않는다. 새로운 입력을 처리하려면 반드시 새 객체를 만들어야 한다.

3-4. 자주하는 실수들

StringBuilder에서 println 사용

// ❌ 잘못된 방법
StringBuilder sb = new StringBuilder();
sb.append("Hello");
System.out.println(sb);  // 불필요한 줄바꿈 추가

// ✅ 올바른 방법
System.out.print(sb);

 

마지막 공백/줄바꿈 처리

// 문제에서 "마지막에 공백 없이" 라고 할 때
StringBuilder sb = new StringBuilder();
for(int i = 0; i < n; i++) {
    sb.append(arr[i]);
    if(i < n-1) sb.append(' ');  // 조건 확인 필수!
}
// 마지막에 \n 붙일지 문제 조건 확인

 

IOException 처리 깜빡하기

// ❌ 컴파일 에러
public static void main(String[] args) {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
}

// ✅ 정상
public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
}
  • PrintStream.println(Object)
    → 문자열 뒤에 줄바꿈 포함한다.
  • PrintStream.print(Object)
    → 줄바꿈 없이 출력한다.

 

 

4. 실전 응용 예제

4-1. 바둑판 입력 (BFS/DFS용)

int n = Integer.parseInt(br.readLine());
char[][] map = new char[n][n];
for(int i = 0; i < n; i++) {
    String line = br.readLine();
    for(int j = 0; j < n; j++) {
        map[i][j] = line.charAt(j);
    }
    // 또는 map[i] = line.toCharArray();
}

4-2. 그래프 인접리스트 입력

int n = Integer.parseInt(br.readLine());  // 정점 수
int m = Integer.parseInt(br.readLine());  // 간선 수

List<Integer>[] graph = new ArrayList[n+1];
for(int i = 1; i <= n; i++) {
    graph[i] = new ArrayList<>();
}

for(int i = 0; i < m; i++) {
    StringTokenizer st = new StringTokenizer(br.readLine());
    int a = Integer.parseInt(st.nextToken());
    int b = Integer.parseInt(st.nextToken());
    graph[a].add(b);
    graph[b].add(a);  // 무방향 그래프인 경우
}
  • ArrayList
    → 동적 배열로, add() 메서드를 통해 원소를 추가할 수 있다.

 


 

 

5. 주의사항

5-1. StringTokenizer는 레거시 클래스

Java SE API 문서에 따르면 StringTokenizer는 "호환성을 위해 유지되는 레거시 클래스"이며, 새로운 코드에서는 String.split() 사용을 권장한다.

하지만 알고리즘 문제에서는 성능상 이유로 여전히 StringTokenizer를 사용하기도 한다.


5-2. BufferedReader의 readLine() 동작

Java SE API 문서에 따르면 readLine()은 "한 줄의 텍스트를 읽는다"고 명시되어 있다.

일반적으로 줄바꿈 문자를 만나면 그 전까지의 문자열을 반환하며, 파일 끝에 도달하면 null을 반환한다.


5-3. Integer.parseInt() 예외 처리

실제 프로젝트에서는 NumberFormatException 처리가 필요하지만, 알고리즘 문제에서는 입력이 보장되므로 생략한다.

 

  • Integer.parseInt()
    → 문자열이 숫자 형식이 아닐 경우 NumberFormatException 발생~

5-4. close() 관련

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
  • BufferedReader / BufferedWriter와 close() 관계
    • BufferedReader는 Reader(문자 입력 스트림)를 BufferedWriter는 Writer(문자 출력 스트림)를 감싼 래퍼 클래스다.
    • 실제 리소스를 소유한 건 System.in / System.out
    • 따라서 br.close()나 bw.close()를 호출하면 내부적으로 원본 스트림(System.in, System.out)까지 닫힌다.

 

  • System.in / System.out
    → System.in, System.out, System.err는 JVM이 종료 시 자동으로 닫히도록 보장된다.
    →  따라서 코딩테스트 환경에서는 BufferedReader, BufferedWriter를 close() 하지 않아도 입력/출력에 문제가 발생하지 않는다.

 

  • 실무와 차이점
    → 실무에서는 파일, 네트워크 소켓 등 외부 리소스를 다룰 때 반드시 close() 또는 try-with-resources로 닫아줘야 리소스 누수 문제가 없다.
    → 하지만 코딩테스트에서는 단일 실행 후 즉시 프로그램이 종료되므로 close() 생략해도 무방하다.

 

 

6. 마무리

정리

  1. 입력: 2차원 배열, 문자 처리, EOF 등 다양한 패턴 숙지
  2. 출력: StringBuilder 위주, 필요시 BufferedWriter 고려
  3. 성능: StringTokenizer > split(), StringBuilder > println
  4. 실수 방지: 매번 new StringTokenizer, 마지막 공백 주의

 

 

 


 

 

7. 참고자료

Java SE 8 API 공식 문서

  • BufferedReader - 버퍼링된 문자 입력 스트림, readLine() 메서드 상세
  • StringTokenizer - 레거시 클래스, split() 사용 권장
  • StringBuilder - 가변 문자열 버퍼, append() 메서드들
  • InputStreamReader - 바이트 스트림을 문자 스트림으로 변환
  • BufferedWriter - 버퍼링된 문자 출력 스트림
  • OutputStreamWriter - 문자 스트림을 바이트 스트림으로 변환

성능 관련 참고

  • StringTokenizer가 split()보다 빠른 이유: 정규식 엔진을 사용하지 않음
  • StringBuilder vs String 연결: StringBuilder는 내부 버퍼를 사용하여 효율적

'공부일기.. > Java' 카테고리의 다른 글

[java] 예외 처리와 트랜잭션 롤백 (PSA (Portable Service Abstraction))  (0) 2025.10.29
[java] Enum 추상 메서드 활용과 이해  (0) 2025.10.03
[java] 자바 파일 처리 기본기 가이드 - 주니어 개발자 필수 FILE/FILES  (0) 2025.09.22
[java] static 제대로 활용하기 - 패턴과 함정  (0) 2025.09.21
[java] static 키워드 - 제약사항~  (0) 2025.09.20
'공부일기../Java' 카테고리의 다른 글
  • [java] 예외 처리와 트랜잭션 롤백 (PSA (Portable Service Abstraction))
  • [java] Enum 추상 메서드 활용과 이해
  • [java] 자바 파일 처리 기본기 가이드 - 주니어 개발자 필수 FILE/FILES
  • [java] static 제대로 활용하기 - 패턴과 함정
s0-0mzzang
s0-0mzzang
공부한것을 기록합니다...
  • s0-0mzzang
    승민이의..개발일기..🐰
    s0-0mzzang
  • 전체
    오늘
    어제
    • 전체~ (108)
      • 마음가짐..! (10)
      • 공부일기.. (76)
        • weekly-log (6)
        • Spring (19)
        • Java (18)
        • DataBase (10)
        • git (2)
        • JPA (6)
        • kafka (1)
        • Backend Architecture (3)
        • Troubleshooting (삽질..ㅋ) (2)
        • Cloud (1)
        • Docker (2)
        • 알고리즘 (1)
        • 리액트 (2)
        • Infra (3)
      • 하루일기.. (22)
        • 그림일기 (8)
        • 생각일기 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    항해99
    인프라 기초
    spring boot
    swagger
    BufferedReader
    자바
    Paging
    스프링부트
    ADC 환경
    다짐
    StringTokenizer
    TDD
    ERD
    리팩토링
    항해플러스
    SpringBoot
    React
    JPA
    spring
    MySQL
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
s0-0mzzang
[코테] JAVA 백준 알고리즘 시작하기 - 입출력 가이드 !!성능최적화!!
상단으로

티스토리툴바