포스트

정규표현식

정규표현식

Java 개발을 하다보면 특정 패턴이 반복되는 자연어를 처리할 일이 많습니다. 개인정보에 대한 마스킹, 로그의 대한 패턴 분석

그럴때마다 parse를 통해 분석을 할 수 있겠지만 복잡한 문자열 검색이나 치환 작업을 간단한 패턴으로 대체할 수 있는 방식으로 정규 표현식(Regular Expression, Regex)를 사용하면 가독성 좋은 코드를 작성할 수 있습니다.

주요 문자에 대한 패턴은 아래 표와 같습니다.

기본 문자와 메타 문자

패턴의미예제
.임의의 한 문자 (줄바꿈 제외)a.c → “abc”, “axc”
^문자열의 시작^Hello → “Hello world”
$문자열의 끝world$ → “Hello world”
\d숫자 (0-9)\d{3} → “123”
\D숫자가 아닌 문자\D+ → “abc”
\w문자, 숫자, 언더스코어\w+ → “hello_123”
\W\w가 아닌 문자\W+ → “!@#”
\s공백 문자 (스페이스, 탭, 줄바꿈)\s+ → “ “
\S공백이 아닌 문자\S+ → “hello”

수량자 (Quantifiers)

패턴의미예제
*0회 이상 반복ab* → “a”, “ab”, “abbb”
+1회 이상 반복ab+ → “ab”, “abbb”
?0회 또는 1회ab? → “a”, “ab”
{n}정확히 n회a{3} → “aaa”
{n,}n회 이상a{3,} → “aaa”, “aaaa”
{n,m}n회 이상 m회 이하a{2,4} → “aa”, “aaa”, “aaaa”

문자 클래스

패턴의미예제
[abc]a, b, c 중 하나[abc] → “a”, “b”, “c”
[a-z]a부터 z까지[a-z]+ → “hello”
[A-Z]A부터 Z까지[A-Z]+ → “HELLO”
[0-9]0부터 9까지[0-9]+ → “123”
[^abc]a, b, c가 아닌 문자[^0-9] → 숫자가 아닌 문자

예제1 : 전화번호 파싱하기

010-6276-5961같은 전화번호를 010, 6276 , 5961로 패턴을 나눌려면

1
String phoneNumber = "010-6276-5961";
  • \d{3}-\d{4}-\d{4} 의 패턴으로 표현될 수 있습니다.

예제2 :이메일 검증하기

사용자가 입력한 이메일에 대해 검증성을 확보하고 싶다면 아래와 같은 정규식을 만들 수 있습니다.

1
"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"

이메일 앞부분 검증하기

  • ^는 @ 앞의 문자열이 반드시 이 뒤의 패턴으로 시작해야함을 알린다.
  • a-z , A-Z, 0-9의 범위내 문자열만 들어와야한다.
  • ._%+-의 문자열도 들어올 수 있다.
  • +이러한 문자열 패턴이 1회이상 반복해야한다.

이메일 뒷부분 검증하기

이메일 뒷부분의 도메인 앞 부분까지 검증한다.

  • a-z , A-Z, 0-9의 범위내 문자열만 들어와야한다.
  • .-까지는 들어올 수 있다.
  • +이러한 문자열 패턴이 1회이상 반복해야한다.

도메인 검증하기

  • a-z , A-Z 의 범위내 문자열만 들어와야한다.
  • {2,} 2글자 이상 들어와야한다!
  • $문자열이 반드시 이러한 패턴으로 끝나야한다!

Java에서의 사용

Java에서는 \d를 사용하기 위해서는 \\d 형태로 사용해야하는데 Java내에서 \는 escape 문자로 분류되기에 \\로 사용할 수 있습니다.

정규 표현식은 강력하지만 표현이 명확하지 않아 가독성이 좋지 않으며 복잡한 정규 표현식은 오히려 성능에 영향을 줄 수 있습니다.

Java 내부의 String 클래스에서 정규 표현식을 바로 지원하는 3개의 메소드가 있습니다.

메서드반환 타입설명
matches(String regex)boolean인자로 주어진 정규식에 매칭되는 값이 있는지 확인
replaceAll(String regex, String replacement)String문자열내에 있는 정규식 regex와 매치되는 모든 문자열을 replacement 문자열로 바꾼 문자열을 반환
split(String regex)String[]인자로 주어진 정규식과 매치되는 문자열을 구분자로 분할

또한 Java에서는 java.util.regex 패키지를 통해 정규 표현식을 지원합니다.

실제 사용사례 - Log 분석

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
로그 파일을 읽어와 로그 패턴 파싱을 수행하고 기본 통계를 출력

예시: [2024-12-30 14:23:45] ERROR UserService Login failed for user: admin

## 출력 정보
로그 레벨별 개수 (INFO, WARN, ERROR, DEBUG)
가장 많이 호출된 메서드 TOP 3
시간대별 로그 분포 (시간대: 00~23시)

출력 포맷:
=== Log Analysis Result ===
Log Level Statistics:
INFO: 150
WARN: 45
ERROR: 23
DEBUG: 82

Top 3 Methods:
1. UserService: 45
2. PaymentService: 32
3. OrderService: 28

Hourly Distribution:
00: 5, 01: 2, 02: 1, ..., 23: 12

※ 로그 파일 형식정보
파일 형식: [YYYY-MM-DD HH:mm:ss] <LEVEL> <METHOD> <MESSAGE

특정 형식을 갖는 로그 파일을 정규 표현식을 통해 특정 부분만 가져올 필요가 있다. 이에 따라 아래와 같이 로그 정보를 정규 표현식을 사용해 표현한다.

1
2
private static final String LOG_PATTERN =
            "\\[\\d{4}-\\d{2}-\\d{2} (\\d{2}):\\d{2}:\\d{2}\\] ([A-Z]+) (\\w+).*";

() 를 통해 특정 로그의 그룹화를 사용한다. 이런 그룹화는 문자열에서 특정 부분만 가져올 때 자주 사용되는 패턴이다.

문제 풀이

1
2
3
4
5
6
7
8
9
10
11
12
13
Pattern pattern = Pattern.compile(LOG_PATTERN);
        // 파일을 한 줄씩 읽기
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {      // 한 줄씩 읽음
                Matcher matcher = pattern.matcher(line);
                matcher.matches();

                String time = matcher.group(1);
                String logLevel = matcher.group(2);
                String service = matcher.group(3);
            }
        }

파일을 한줄씩 읽은 뒤, Matcher.matches();를 통해 그룹화를 실시한다. 그리고 matcher.group(n)을 통해 원하는 문자열의 부분만 별도의 변수에 저장하여 사용할 수 있다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.