외부활동/우아한테크코스 [프리코스]

자바의 정규표현식 톺아보기[Regex, Matcher, Pattern] 및 최적화

softmoca__ 2024. 10. 23. 15:54
목차

Regex(정규표현식, Regular Expression)란?

Regex는 문자열에서 특정 패턴을 검색, 일치, 변환, 분리하기 위한 표현식으로 문자열을 효율적으로 처리할 수 있도록 설계된 규칙 기반의 언어이며 주로 아래 5개의 목적을 위해 사용된다.

  1. 검색(Search): 특정 패턴이 문자열 내에 존재하는지 확인.
  2. 일치(Match): 문자열이 특정 패턴과 완전히 일치하는지 확인.
  3. 추출(Extract): 문자열에서 특정 부분을 추출.
  4. 분리(Split): 특정 패턴을 기준으로 문자열을 분리.
  5. 변환(Replace): 특정 패턴을 찾아 다른 값으로 대체

Regex의 기본 구성 요소

Regex는 문자와 메타문자로 구성된다.

1. 문자 (Literal Characters)

  • 특정 문자나 문자열 자체를 찾는다.
  • 예: "abc"는 문자열에 "abc"가 있는지 찾는다.

2. 메타문자 (Metacharacters)

메타문자는 특별한 의미를 가지는 문자들이다.

. 임의의 한 문자 (줄바꿈 제외) "a.c"는 "abc", "adc"
^ 문자열의 시작 "^abc"는 "abc"로 시작
$ 문자열의 끝 "xyz$"는 "xyz"로 끝남
* 0회 이상 반복 "a*"는 "a", "aa"
+ 1회 이상 반복 "a+"는 "a", "aa"
? 0회 또는 1회 "a?"는 "a" 또는 없음
{n} 정확히 n번 반복 "a{3}"는 "aaa"
{n,} 최소 n번 반복 "a{2,}"는 "aa", "aaa"
{n,m} 최소 n번, 최대 m번 반복 "a{2,4}"는 "aa", "aaa", "aaaa"
[] 문자 클래스. 괄호 안의 문자 중 하나와 일치 "[abc]"는 "a", "b", "c"
[^] 부정 문자 클래스. 괄호 안의 문자가 아닌 문자와 일치 "[^abc]"는 "d", "e"
` ` OR 연산자
() 그룹화 (캡처 그룹) "(abc)"는 그룹으로 캡처
\ 이스케이프 문자. 메타문자를 일반 문자로 취급 "\."는 "." 찾기

Regex의 특수 시퀀스

1. 문자 클래스 (Character Classes)

  • 특정 문자 유형을 지정한다.

표현식의미예시

\d 숫자 ([0-9]) "123"
\D 숫자가 아님 ([^0-9]) "abc"
\w 단어 문자 ([a-zA-Z0-9_]) "word1"
\W 단어 문자가 아님 "!@#"
\s 공백 문자 " "
\S 공백 문자가 아님 "abc"

2. 앵커 (Anchors)

  • 특정 위치를 기준으로 패턴을 일치시킨다.
^ 문자열의 시작 ^Hello는 "Hello World"에서 "Hello" 찾음
$ 문자열의 끝 World$는 "Hello World"에서 "World" 찾음

 

 

Pattern 클래스

  • 정규표현식 패턴을 정의하고 이를 컴파일하는 역할을 한다.
  • 정적 메서드로 정규표현식을 컴파일하고 Matcher 객체를 생성하는 데 사용된다.
  • Pattern 객체는 한 번 컴파일되면 재사용이 가능하므로, 여러 번 매칭해야 할 경우 성능이 좋다.

compile(String regex)

  • 정규표현식을 컴파일하여 Pattern 객체를 반환한다.
  • 정규식의 문법 오류가 있을 경우 PatternSyntaxException이 발생한다.
Pattern pattern = Pattern.compile("\\d+"); // 숫자 하나 이상

 

matcher(CharSequence input)

  • 입력 문자열에 대해 매칭을 수행할 Matcher 객체를 생성한다.
Matcher matcher = pattern.matcher("123abc");

split(CharSequence input)

  • 정규표현식을 기준으로 문자열을 분리한다.
String[] result = pattern.split("apple,banana,orange");

pattern()

  • Pattern 객체에 저장된 정규표현식을 반환한다.

 

Matcher 클래스

  • 입력 문자열과 정규표현식을 매칭하는 데 사용된다.
  • 매칭된 패턴의 위치, 결과, 그룹을 확인할 수 있다.

find()

  • 입력 문자열에서 정규식과 일치하는 다음 부분을 찾는다.
  • 반환값: boolean (일치 여부)
 
while (matcher.find()) {
    System.out.println(matcher.group());
}

 

matches()

  • 입력 문자열 전체가 정규식과 일치하는지 확인한다.
  • 반환값: boolean
boolean isMatch = matcher.matches();
 
group()
  • 매칭된 문자열을 반환합니다.
  • 그룹화된 부분이 있으면 특정 그룹을 반환할 수도 있다.
System.out.println(matcher.group(1)); // 첫 번째 그룹 반환
start()와 end()
  • 매칭된 부분의 시작 인덱스끝 인덱스를 반환한다.
System.out.println("Start: " + matcher.start() + ", End: " + matcher.end());
 
replaceAll(String replacement)
  • 정규식에 매칭되는 부분을 지정된 문자열로 치환한다.
 
String result = matcher.replaceAll("replaced");
 
 

 

 

효율적인 Pattern 객체 사용으로 메모리 낭비 줄이기

1주차 문자열계산기 미션의 나의 코드 중 최적화 할수 있는 부분에 대해 피드백을 받았다.

 

Pattern pattern = Pattern.compile("//(.)\\\\n.*");
Matcher matcher = pattern.matcher(input);

나의 코드에는 위와같이 여러 메서드에서 매번 새로운 Pattern 객체를 생성하고 있어, 불필요한 객체를 무분별하게 생성하며 메모리 낭비가 일어나 성능 저하가 발생하고 있다.

 

 

🔨 개선

  // 정규표현식 패턴을 상수로 정의
    private static final Pattern CUSTOM_DELIMITER_PATTERN = Pattern.compile("//(.)\\\\n.*");
    private static final Pattern NUMBERS_AFTER_DELIMITER_PATTERN = Pattern.compile("//.\\\\n(.*)");
    private static final Pattern DEFAULT_PATTERN = Pattern.compile("^[0-9,:]*$");
    
    public String getCustomDelimiter(String input) {
        Matcher matcher = CUSTOM_DELIMITER_PATTERN.matcher(input);
        if (matcher.find()) {
            String delimiter = matcher.group(1);
            if (delimiter.matches("[0-9]")) {
                throw new IllegalArgumentException("숫자는 구분자로 사용할 수 없습니다.");
            }
            return delimiter;
        }
        throw new IllegalArgumentException("올바른 커스텀 구분자가 없습니다.");
    }

    public String getNumbersAfterDelimiter(String input) {
        Matcher matcher = NUMBERS_AFTER_DELIMITER_PATTERN.matcher(input);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return "";
    }

    public boolean isCustomDelimiterPattern(String input) {
        return CUSTOM_DELIMITER_PATTERN.matcher(input).matches();
    }

    public boolean isValidDefaultPattern(String input) {
        return DEFAULT_PATTERN.matcher(input).matches();
    }

 

위와같이 Pattern 객체를 재사용하여 자주 사용되는 정규표현식 패턴을  priavte static final로 선언하여 클래스 상수로 정의하며 불변성을 보장 받으며 매 메서드 호출마다 새로운 Pattern 개체를 생성하지 않아도 되어 메모리 효율성과 성능 향상을 이룰수 있다.