백준에서 자바를 작성해볼려고 시도하였으나 처음 템플릿 작성부터 많은 어려움을 겪었습니다. 이를 해결하면서 얻은 지식을 공유해볼려고 합니다.
백준에서 자바를 작성하기 위해서는 기초 템플릿을 직접 작성해야 합니다. 이는 IDE 환경과 다른 코테 사이트(릿코드, 프로그래머스 등)만 접해온 사람들에게 가혹합니다. 그래도 성장할려면 직접 작성하는 법도 알아야 하는 법입니다. 템플릿은 다음과 같습니다.
public class Main {
public static void main(String[] args) {
}
}
그럼 기초 템플릿도 알았으니 java의 헬로 월드에 도전해봅니다.
https://www.acmicpc.net/problem/2557
public class Main {
public static void main(String[] args) {
System.out.print("Hello World!");
}
}
결과는 정답입니다.

하지만 이 코드는 코테 환경에서 성능에 영향을 줄 수 있습니다. 바로 "System.out.println()" 때문입니다. 이와 비슷하게 Java의 "Scanner"도 성능에서 좋은 영향을 미치지 못할 수도 있습니다.
그럼 출력 성능을 개선하기 위해서 사용해야 하는 코드는 무엇일까요? 바로 "BufferedWriter"와 "BufferedReader"입니다.
먼저 BufferedWriter의 예시로 HelloWorld 예시를 고쳐봅시다.
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
bw.write("Hello World!");
bw.flush();
bw.close();
}
}
코드가 상당히 복잡해졌습니다....
차이점을 요약해봅시다.
import java.io.*;
성능이 좋은 BufferedReader와 BufferedOuter를 사용하기 위해서는 java.io에서 해당 함수를 가져와야 합니다.
throws IOException
java.io를 사용하는 값을 이용하기 위해서는 해당 예외를 처리해줘야 합니다. 따라서 사용하는 함수에 예외 처리를 해줍니다.
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
해당 writer의 변수의 선언입니다. BufferedWriter를 사용하기 위해서는 변수를 생성해야 함으로 이 문장을 기억해둡니다.
bw.write("Hello World!"); bw.flush(); bw.close();
bw의 사용입니다. bw.write는 출력해야할 값을 String으로 입력해줍니다.
bw.flush는 화면에 해당 값을 출력해줍니다.
사용이 끝난 bw는 종료해줍니다.
요런 복잡함을 거치고 나면 성능이 개선된 것을 알 수 있습니다.

결과를 보면 이전에 비해 4ms가 감소한 것을 알 수 있습니다. 이는 출력해야 하는 양이 길어질수록 좋은 성능을 보일 것이라고 생각합니다.
다음으로 BufferedReader에 대해서 알아봅시다.
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 정수 하나
int num = Integer.parseInt(br.readLine());
// 문자열 하나
String str = br.readLine();
br.close();
}
}
위는 BufferedReader의 기초 template입니다. 기본적으로는 BufferedWriter와 비슷하나 BufferedReader의 변수 선언과 사용 목적이 writer와 다릅니다. java.io를 사용하는 것과 IOException을 사용하는 것이 공통점이라고 할 수 있습니다. 또한 br.close()를 통해 사용 후 닫아주는 과정도 중요합니다.
BufferedReader는 기본적으로 "readLine()"을 통해 String 형태로 입력한 한 줄을 가져오는 데 입력한 값이 하나라면 그대로 대입해주고 만약 필요한 값이 Int라면 Integer.parseInt를 통해 int로 변환시킨 후 대입해주면 됩니다.
그럼 2개 이상의 값이 한줄에 들어오면 어떻게 해야 할까요?
바로 "split()" 함수를 이용하여 " "을 기준으로 값을 분리하거나 StringTokenizer로 값을 분리해서 사용해주면 됩니다.
StringTokenizer를 사용하기 위해서는 java.util에서 StringTokenizer를 가져와야 합니다.
깨알 상식
(StringTokenizer Vs split()
둘중에 무엇을 쓰는게 좋을까요?
일반적인 입력 상황에서 정답은 StringTokenizer입니다. Split()은 정규식을 기준으로 문자열을 분리하기 때문에 단일 구분자(기본값은 공백)을 기준으로 분리하는 StringTokenizer에 비해 느립니다. 따라서 구분이 복잡하지 않은 경우는 StringTokenizerf를 사용합시다.
)
StringTokenizer(추천)
import java.io.*;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
while (st.hasMoreTokens()) {
st.nextToken(); // 이를 이용해서 작업은 진행
}
br.close();
}
}
Split(비추천)
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] parts = br.readLine().split(" ");
for (int i = 0; i < parts.length(); i++) {
Integer.parseInt(parts[i]); // 이를 이용해서 작업 진행
}
br.close();
}
}
그리고 여러줄을 통해 입력을 받을 때는 for이나 while문을 사용합니다.
백준에서 몇 줄을 입력할 지 미리 알려준다면 해당 값을 받아 for문을 돌리면 됩니다.
import java.io.*;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
for (int i = 0; i < n; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
while (st.hasMoreTokens()) {
st.nextToken(); // 값을 원하는 곳에 저장하여 사용
}
}
br.close();
}
}
만약 몇줄이 입력될 지 모른다면 while문을 사용해야 합니다. 이런 경우는 적을 것이라고 생각됩니다.
import java.io.*;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = br.readLine()) != null && !line.isEmpty()) {
StringTokenizer st = new StringTokenizer(line);
while (st.hasMoreTokens()) {
st.nextToken(); // 값을 원하는 곳에 저장하여 사용
}
}
br.close();
}
}
이 코드의 단점은 빈줄의 있을 경우에도 멈춘다는 것입니다. 그럼에도 백준에서 사용하기에는 충분해 보입니다.
위의 정보들을 템플릿을 문제를 풀면 될 것 같습니다.
다른 사람들의 작성한 풀이를 보면 백준의 main 함수는 정답 함수의 호출만을 담당하고 정답 함수에서 로직을 처리하는 구조가 많이 보입니다.
제 생각에 이렇게 처리하는 이유는 가독성인 것 같습니다. main은 정답 함수를 출력하는 곳으로만 처리를 하여 로직을 짤 때 가독성을 높일 수 있으면 역할을 분리할 수 있는 것처럼 말입니다.
또한 여러개의 풀이를 비교하기 위해 main과 solution을 분리한다는 의견과 다른 사이트에서와 IDE 환경에서의 로직 재사용을 위해 분리한다는 의견도 존재합니다.
만약 main에서 모든 것을 처리하는 것이 편한 사람이라면 위의 이유들을 고려하여 원하는 방식을 선택하면 좋을 것 같습니다.
아래는 함수 분리 template으로 작성한 "Hello World!"문제입니다.
import java.io.*;
public class Main {
public static void solution() throws IOException {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
bw.write("Hello World!");
bw.flush();
bw.close();
}
public static void main(String[] args) throws IOException {
solution();
}
}
백준 기다려라 내가 간다.