정규표현식
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)
을 통해 원하는 문자열의 부분만 별도의 변수에 저장하여 사용할 수 있다.