[기본 개념] 4 | (2.2) Java.util.regex 패키지, Java.util.Scanner 클래스

728x90

[기본 개념] 4 | (2.2) Java.util.regex 패키지, Java.util.Scanner 클래스

1 java.util.Objects클래스

2 java.util.Random클래스

3> 정규식(Regular Expression) - java.util.regex패키지

4> java.util.Scanner클래스

5 java.util.StringTokenizer클래스

6 java.math.BigInteger클래스

7 java.math.BigDecimal클래스

3. 정규식(Regular Expression) - java.util.regex패키지

 정규식이란 텍스트 데이터 중에서 원하는 조건(패턴)과 일치하는 문자열을 찾아내기 위해 사용하는 것으로 미리 정의된 기호와 문자를 이용해서 작성한 문자열을 말한다.

 

 정규식을 이용하면 많은 양의 텍스트 파일 중에서 원하는 데이터를 손쉽게 뽑아낼 수 있고, 입력된 데이터가 형식에 맞는지 체크할 수 있다.

 

 Pattern은 정규식을 정의하는 데 사용되고 Matcher는 정규식(패턴)을 데이터와 비교하는 역할을 한다. 정규식을 정의하고 데이터를 비교하는 과정은 다음과 같다.

 

1 정규식을 매개변수로 Pattern클래스의 static메서드인 Pattern compile(String regex)을 호출하여 Pattern인스턴스를 얻는다.

    Pattern p = Pattern.compile("c[a - z]*") ;

2 정규식으로 비교할 대상을 매개변수로  Pattern클래스의 Matcher matcher(CharSequence input)를 호출해서 Matcher인스턴스를 얻는다.

    Matcher m = p.matcher(data[i]) ;

3 Matcher인스턴스에 boolean matches( )를 호출해서 정규식에 부합하는지 확인한다.

    if (m.matches( ))

 

자주 쓰일 만한 패턴들이다.

 

정규식 패턴 설명 결과
c[a - z]* c로 시작하는 영단어 c, ca, co, car, combat, count,
c[a - z] c로 시작하는 두 자리 영단어 ca, co,
c[a - zA - z] c로 시작하는 두 자리 영단어
(대소문자 구분안함)
cA, ca, co,
c[a - zA - Z0 - 0]
c\w
c로 시작하고 숫자와 영어로 조합된 두 글자 cA, ca, c0, co,
.* 모든 문자열 bat, baby, bonus, c, cA, ca, co, c., c0, c#,
car, combat, count, date, disc,
c. c로 시작하는 두 자리 문자열 cA, ca, co, c., c0, c#,
c.* c로 시작하는 모든 문자열 (기호포함) cA, ca, co, c., c0, c#, car, combat, count,
c\. c.와 일치하는 문자열'.'은 패턴작성에 사용되는 문자이므로 escape문자인 '\'를 사용해야 한다. c.,
c\d
c[0 - 9]
c와 숫자로 구성된 두 자리 문자열 c0,
c.*t c로 시작하고 t로 끝나는 모든 문자열 combat, count,
[b | c].*
[bc].*
[b - c].*
b 또는 c로 시작하는 문자열 bat, baby, bonus, c, cA, ca, co, c., c0, c#,
car, combat, count,
[^b | c].*
[^bc].*
[^b - c].*
b 또는 c로 시작하지 않는 문자열 date, disc,
.*a.* a를 포함하는 모든 문자열
* : 0 또는 그 이상의 문자
bat, baby, ca, car, combat, date,
.*a.+ a를 포함하는 모든 문자열
+ : 1 또는 그 이상의 문자. '+'는 '*'과는 달리 반드시 하나 이상의 문자가 있어야 하므로 a로 끝나는 단어는 포함되지 않았다.
bat, baby, car, combat, date,
[b | c].{2} b 또는 c로 시작하는 세 자리 문자열
(b 또는 c 다음에 두 자리이므로 총 세 자리)
bat, car,

 

예제/RegularEx3.java

import java.util.regex.*;

public class RegularEx3 {
    public static void main(String[] args) {
        String source = "HP:011-1111-1111, HOME:02-999-9999";
        String pattern = "(0\\d{1,2})-(\\d{3,4})-(\\d{4})";

        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(source);

        int i = 0;
        while (m.find()) {
            System.out.println(++i + ": " + m.group() + " -> " + m.group(1)
                    + ", " + m.group(2) + ", " + m.group(3));
        }
    }
}
실행결과

1: 011-1111-1111 -> 011, 1111, 1111
2: 02-999-9999 -> 02, 999, 9999

 

 이처럼 정규식의 일부를 괄호로 묶어서 그룹화(grouping)할 수 있다. 그룹화된 부분은 하나의 단위로 묶이는 셈이 되어서 한 번 또는 그 이상의 반복을 의미하는 '+'나 '*'가 뒤에 오면 그룹화된 부분이 적용대상이 된다. 그리고 그룹화된 부분은 group(int i)를 이용해서 나누어 얻을 수 있다.

 

위의 예제에서 '(0\\d{1,2})-(\\d{3,4})-(\\d{4})'은 괄호를 이용해서 정규식을 세 부분으로 나누었다.

 

0\\d{1,2}  : 0으로 시작하는 최소 2자리 최대 3자리 숫자 (0포함)

\\d{3,4}     : 최소 3자리 최대 4자리의 숫자

\\d{4}         : 4자리의 숫자

 

 find( )은 주어진 소스 내에서 패턴과 일치하는 부분을 찾아내면 true를 반환하고 찾지 못하면 false를 반환한다. find( )을 호출해서 패턴과 일치하는 부분을 찾아낸 다음, 다시 호출하면 이전에 발견한 패턴과 일치하는 부분의 다음부터 다시 패턴 매칭을 시작한다.

 

 Matcher의 find( )로 정규식과 일치하는 부분을 찾으면, 그 위치를 start( )와 end( )로 알아낼 수 있고, appendReplacement(StringBuffer sb, String replacement)를 이용해서 원하는 문자열로 치환할 수 있다.

 

String source = "A broken hand works, but not a broken heart." ;

String pattern = "broken" ;

. . .

Matcher m = p.matcher(source)

. . .

    m.appendReplacement(sb, "drunken") ;

    . . .

m.appendTail(sb) ;

. . .

 

1 문자열 source에서 "broken"을 m.find( )로 찾은 후 처음으로 m.appendReplacement(sb, "drunken") ;가 호출되면 source의 시작부터 "broken"을 찾은 위치까지의 내용에 "drunken"을 더해서 저장한다.

- sb에 저장된 내용 : "A drunken"

2 m.find( )는 첫 번째로 발견된 위치의 끝에서부터 다시 검색을 시작하여 두 번째 "broken"을 찾게 된다. 다시 m.appendReplacement(sb, "drunken") ;가 호출된다.

- sb에 저장된 내용 : "A drunken hand works, but not a drunken"

3 m.appendTail(sb) ;이 호출되면 마지막으로 치환된 이후의 부분을 sb에 덧붙인다.

- sb에 저장된 내용 : "A drunken hand works, but not a drunken heart."

4.  java.util.Scanner클래스

 Scanner는 화면, 파일, 문자열과 같은 입력 소스로부터 문자데이터를 읽어오는데 도움을 주며 다양한 입력소스로부터 데이터를 읽을 수 있다.

 

Scanner(String source)

Scanner(File source)

Scanner(InputStream source)

Scanner(Readable source)

Scanner(ReadableByteChannel source)

Scanner(Path source)

 

 또한 정규식 표현을 이용한 라인 단위의 검색을 지원하며 구분자(delimiter)에도 정규식 표현을 사용할 수 있어서 복잡한 형태의 구분자도 처리가 가능하다.

 

 이와 같은 메서드를 제공함으로 입력받은 문자를 다시 변환하지 않아도 된다. 데이터 형식에 맞지 않는 메서드를 사용하면 InputMismatchException이 발생한다.

 

boolean nextBoolean( )

byte nextByte( )

short nextShort( )

int nextInt( )

long nextLong( )

double nextDouble( )

float nextFloat( )

String nextLine( )

 

예제/ScannerEx3.java

import java.util.Scanner;
import java.io.File;

public class ScannerEx3 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(new File("data3.txt"));
        int cnt = 0;
        int totalSum = 0;

        while (sc.hasNextLine()) {
            String line = sc.nextLine();
            Scanner sc2 = new Scanner(line).useDelimiter(",");
            int sum = 0;

            while (sc2.hasNextLine()) {
                sum += sc2.nextInt();
            }
            System.out.println(line + ", sum = " + sum);
            totalSum += sum;
            cnt++;
        }
        System.out.println("Line: " + cnt + ", Total: " + totalSum);
    }
}
실행결과

100,100,100, sum = 300
200,200,200, sum = 600
300,300,300, sum = 900
400,400,400, sum = 1200
500,500,500, sum = 1500
Line: 5, Total: 4500

c:\jdk1.8\. . .>type data3.txt
100,100,100
200,200,200
300,300,300
400,400,400
500,500,500

 

 이 예제는 파일로부터 데이터를 읽어서 계산하는 것이다. 근데 ','를 구분자로 한 라인에 여러 데이터가 저장되어 있다. 이럴때는 파일의 내용을 먼저 라인별로 읽은 다음, 다시 ','를 구분자로 하는 Sacnner를 이용해서 각각의 데이터를 읽어야 한다.

 

또한 소스파일과 다른 디렉토리에 위치한 파일을 읽기 위해서는 파일명에 경로도 함께 지정해주어야 한다.

 

 

 

 

 

출처 | Java의 정석 (남궁 성)

728x90