[입출력]01. Scanner
이번 포스트에서는 자바에서 입력을 가장 손쉽게 처리할 수 있는 Scanner에 대해서 알아보자.
Scanner
기본 사용법
Scanner를 만들기 위한 생성자는 InputStream 타입의 파라미터 source를 받는다.
public Scanner(InputStream source) {...}
이 source가 어디에서 자료를 받을것인지를 결정하는데 일반적으로 키보드에서 받을 경우 System.in, 파일로 부터 받을 경우 FileInputStream이 사용된다.
Scanner에서 데이터를 읽어들일 때는 일반적으로 hasNextXXX() --> nextXXX()의 두 단계를 거친다.
여기서 XXX는 데이터 형에 따라 여러 가지 형태가 있다. 각각의 데이터 즉 토큰에 대한 구분은 공백문자('\t',space, '\n')를 사용한다.
예를 들어 다음은 키보드로부터 문자열을 입력받아서 출력하는 예이다.
private static void read0() {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) { // 다음 문자가 있는가?
String next = scanner.next(); // 다음 토큰을 가져옴
if (next.equalsIgnoreCase("x")) {
break;
} else {
System.out.println(next);
}
}
scanner.close(); // Scanner 닫기
}
위 코드를 실행하고 아래 주석에 해당하는 값을 입력시켜보자. [↩︎]는 엔터 키이다.
//hello[↩︎]
hello
//java[↩︎]
java
//world[↩︎]
world
//scanner is easy[↩︎]
scanner
is
easy
hello, java, world는 한 문장당 하나의 단어가 입력되었고 각 단어가 하나의 토큰이다. scanner is easy는 한 문장에 3개의 단어가 입력되었고 역시 각 단어가 하나의 토큰이다.
다양한 입력 소스
Scanner는 여러가지 입력 소스에서 데이터를 받을 수 있다. 앞선 예에서 키보드인 System.in을 사용해봤는데 이번에는 File에 저장된값을 읽어서 처리해보자. 파일에서 값을 읽어올 때는 노드스트림인 FileInputStream을 사용할 수 있다.
위 예에서 키보드에서 입력했던 내용을 c:/Temp/sample_input.txt파일에 작성하고 읽어보자.
hello
java
world
scanner is easy
private static void read1() throws FileNotFoundException {
// 파일에 연결된 InputStream으로 Scanner 생성
Scanner scanner = new Scanner(new FileInputStream("c:/Temp/sample_input.txt"));
while (scanner.hasNext()) {
String next = scanner.next();
if (next.equalsIgnoreCase("x")) {
break;
} else {
System.out.println(next);
}
}
scanner.close();
}
소스를 보면 다른 부분은 모두 동일하다 단지 Scanner를 생성할 때 키보드가 아닌 파일에서 가져왔을 뿐이다.
알고리즘 문제를 풀다보면 입력 값이 길게 주어지는 경우가 많은데 매번 입력하기 보다는 파일로 작성한 후 읽어들이면 손쉽게 입력을 처리할 수 있다.
하지만 파일을 만들고 읽어들이는 것도 상당히 귀찮은 일이다. 알고리즘 문제 풀이 시 가장 편리한 입력 소스는 String을 사용하는 것이다. 문제에서 제공되는 문자열을 복사 후 클래스에 붙여놓고 이 String을 소스로 Scanner를 생성해보자.
private static void read2() throws FileNotFoundException {
// 문자열을 입력으로 Scanner 생성
Scanner scanner = new Scanner(src);
while (scanner.hasNext()) {
String next = scanner.next();
if (next.equalsIgnoreCase("x")) {
break;
} else {
System.out.println(next);
}
}
scanner.close();
}
private static String src = "hello\r\n" +
"java\r\n" +
"world\r\n" +
"scanner is easy";
다양한 자료형 처리
Scanner의 장점은 원하는 자료형에 맞춰 읽을 수 있다는 점이다.
앞서 method를 소개할 때 hasNextXXX(), nextXXX()라고 했던 점을 상기시켜보자.
다음은 Scanner가 갖는 hasNext 계열의 메서드들이다.
당연히 next 계열의 메서드들도 변수 타입에 따라 준비되어있다.
대부분 토큰 단위로 읽어들이고 hasNextLine()과 nextLine()은 한 줄씩 데이터를 읽어들이는 부분이 다르다.
문장단위로 읽어들인 데이터는 StringTokenizer나 String의 split() 메서드를 이용해서 토큰 단위로 쪼개서 사용한다. 이 두 녀석의 사용법은 다음 포스트에서 다룬다.
다음 예는 문자열 형태의 숫자들을 읽어오는 예이다.
// 정수가 아닌 것이 들어오면 반복 종료
private static void read3() {
Scanner scanner = new Scanner(nums);
while (scanner.hasNextInt()) {
int next = scanner.nextInt();
System.out.println(next);
}
scanner.close();
}
private static String nums = "100\r\n" +
"200 300 400\r\n" +
"500\r\n" +
"x";
패턴의 사용
Scanner는 자동으로 입력값을 토큰으로 구분해서 읽어들인다. 이때 활용되는 구분자는 공백문자 즉 space, tab, carriage return이다. 하지만 필요에 따라서 쉼표등 구분자를 이용해야할 때도 있다. 이때는 Scanner의 useDelimiter()를 이용해 구분자로 사용할 패턴을 지정할 수 있다.
다음 예를 살펴보자.
private static void read4() {
// , (쉼표와 공백)을 구분자로 갖는 데이터 처리
Scanner scanner = new Scanner("100, 200, 300, 400");
scanner.useDelimiter(", "); // 사용자 정의 구분자 지정
while (scanner.hasNextInt()) {
int next = scanner.nextInt();
System.out.println(next);
}
scanner.close();
}
위 예는 쉼표와 공백을 구분자로 갖는 일련의 데이터에서 필요한 토큰들을 추출해서 사용할 수 있게 한다.