java.util.regex-Pattern.compile ()의 중요성


118

Pattern.compile()방법 의 중요성은 무엇입니까 ? 객체
를 가져 오기 전에 정규식 문자열을 컴파일해야하는 이유는 무엇 Matcher입니까?

예 :

String regex = "((\\S+)\\s*some\\s*";

Pattern pattern = Pattern.compile(regex); // why do I need to compile
Matcher matcher = pattern.matcher(text);

2
음, 구현 (JDK 1.7에서와 같이)이 단지 new Pattern (regex, 0)에 대한 단순한 SHORTCUT이라면 중요성은 거의 NONE입니다. 즉, REAL 중요성은 정적 메서드 자체가 아니라 나중에 사용하기 위해 저장할 수있는 새 패턴의 생성 및 반환입니다. 정적 메서드가 새 경로를 취하고 Pattern 객체를 캐시하는 다른 구현이있을 수 있으며, 이는 Pattern.compile () 중요성의 실제 사례입니다!
marcolopes 2014

대답은 패턴을 분리하고 클래스를 일치시키는 것의 중요성을 강조하지만 (아마 질문에서 물어 보는 것임) new Pattern(regex)정적 컴파일 함수 대신 생성자 를 사용할 수없는 이유에 대해 아무도 대답하지 않습니다 . marcolopes 댓글이 자리에 있습니다.
kon psych

답변:


144

compile()메서드는 항상 어떤 지점에서 호출됩니다. 이것이 Pattern 객체를 생성하는 유일한 방법입니다. 그래서 질문은 정말로, 왜 그것을 명시 적으로 불러야 합니까? 한 가지 이유는 Matcher 개체에 대한 참조가 필요하므로 group(int)캡처 그룹의 내용을 검색하는 것과 같은 메서드를 사용할 수 있기 때문 입니다. Matcher 개체를 유지 matcher()하는 유일한 방법 은 Pattern 개체의 메서드 를 사용하는 것이며 Pattern 개체를 유지 하는 유일한 방법은 compile()메서드 를 사용하는 것입니다. 그런 다음 find()과 달리 matches()String 또는 Pattern 클래스에서 중복되지 않는 메서드가 있습니다.

다른 이유는 동일한 Pattern 객체를 반복해서 생성하지 않는 것입니다. String에서 정규식 기반 메서드 (또는 matches()Pattern 의 정적 메서드) 중 하나를 사용할 때마다 새 패턴과 새 Matcher가 생성됩니다. 따라서이 코드 스 니펫 :

for (String s : myStringList) {
    if ( s.matches("\\d+") ) {
        doSomething();
    }
}

... 다음과 정확히 동일합니다.

for (String s : myStringList) {
    if ( Pattern.compile("\\d+").matcher(s).matches() ) {
        doSomething();
    }
}

분명히 그것은 많은 불필요한 작업을하고 있습니다. 실제로 실제 일치를 수행하는 것보다 정규식을 컴파일하고 Pattern 객체를 인스턴스화하는 데 더 오래 걸릴 수 있습니다. 따라서 일반적으로 루프에서 해당 단계를 당기는 것이 좋습니다. 거의 비싸지는 않지만 Matcher를 미리 만들 수도 있습니다.

Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("");
for (String s : myStringList) {
    if ( m.reset(s).matches() ) {
        doSomething();
    }
}

.NET 정규식에 익숙하다면 Java의 compile()메서드가 .NET의 RegexOptions.Compiled수정 자 와 관련 이 있는지 궁금 할 것입니다 . 내 대답은 아니오 야. Java의 Pattern.compile()메서드는 .NET의 Regex 생성자와 동일합니다. Compiled옵션 을 지정하는 경우 :

Regex r = new Regex(@"\d+", RegexOptions.Compiled); 

... 정규식을 CIL 바이트 코드로 직접 컴파일하여 훨씬 빠르게 수행 할 수 있지만 선행 처리 및 메모리 사용에 상당한 비용이 소요됩니다. 정규식을위한 스테로이드라고 생각하면됩니다. Java에는 이에 상응하는 것이 없습니다. 에서 만든 패턴과을 String#matches(String)사용하여 명시 적으로 만든 패턴 사이에는 차이가 없습니다 Pattern#compile(String).

(편집 : 원래 모든 .NET Regex 개체가 캐시되어 있다고 말했는데 이는 올바르지 않습니다. .NET 2.0 이후로 자동 캐싱 Regex.Matches()은 Regex 생성자를 직접 호출 할 때가 아니라와 같은 정적 메서드에서만 발생합니다 . ref )


1
그러나 이것은 Pattern 클래스에서 그러한 TRIVIAL 메소드의 중요성을 설명하지 않습니다! 나는 항상 정적 메소드 Pattern.compile이 new Pattern (regex, 0)에 대한 간단한 SHORTCUT 이상이라고 가정했습니다. 나는 컴파일 된 패턴의 캐시를 기대하고 있었다. 나는 틀렸다. 캐시를 만드는 것이 새 패턴을 만드는 것보다 더 비쌀 수도 있습니다. ??!
marcolopes 2014

9
Matcher 클래스는 스레드로부터 안전하지 않으며 스레드간에 공유해서는 안됩니다. 반면에 Pattern.compile ()은입니다.
gswierczynski

1
TLDR; "... [Pattern.compile (...)] 정규식을 CIL 바이트 코드로 직접 컴파일하여 훨씬 빠르게 수행 할 수 있지만
선행

3
Matchers가 Pattern.compile만큼 비싸지는 않지만 수천 개의 정규식 일치가 발생하는 시나리오에서 몇 가지 메트릭을 수행했으며 Matcher를 미리 만들고 matcher를 통해 재사용함으로써 추가적으로 매우 중요한 절약이있었습니다. .초기화(). 수천 번 호출되는 메서드에서 힙에 새 개체 생성을 피하는 것은 일반적으로 CPU, 메모리 및 GC에서 훨씬 가볍습니다.
Volksman

@Volksman은 Matcher 개체가 스레드로부터 안전하지 않기 때문에 안전하지 않은 일반적인 조언입니다. 질문과도 관련이 없습니다. 그러나 예, reset할당을 줄이기 위해 한 번에 하나의 스레드에서만 사용되는 Matcher 개체를 사용할 수 있습니다.
AndrewF

40

컴파일 은 정규 표현식을 파싱 하고 메모리 내 표현을 만듭니다 . 컴파일하는 오버 헤드는 일치에 비해 중요합니다. 패턴을 반복적으로 사용하는 경우 컴파일 된 패턴을 캐시하는 성능이 향상됩니다.


7
또한 추가 플래그 매개 변수를 전달하여 컴파일 중에 case_insensitive, dot_all 등과 같은 플래그를 지정할 수 있습니다
Sam Barnum

17

컴파일 할 때 PatternJava는 Strings 에서 일치 항목을 더 빨리 찾기 위해 몇 가지 계산을 수행합니다 . (정규식의 메모리 내 표현 작성)

Pattern여러 번 재사용하려는 경우 Pattern매번 새로 만드는 것보다 성능이 크게 향상되는 것을 볼 수 있습니다 .

패턴을 한 번만 사용하는 경우 컴파일 단계가 추가 코드 줄처럼 보이지만 실제로는 일반적인 경우에 매우 유용 할 수 있습니다.


5
물론 모든 것을 한 줄로 작성할 수 있습니다 Matcher matched = Pattern.compile(regex).matcher(text);. 단일 메서드를 도입하는 것보다 이점이 있습니다. 인수의 이름이 효과적으로 지정되고 Pattern더 나은 성능 (또는 메서드간에 분할)을 위해 인수를 제거하는 방법이 분명합니다 .
Tom Hawtin-tackline

1
항상 Java에 대해 많이 알고있는 것 같습니다. 그들은 ... 그들을 위해 일에 당신을 고용해야
jjnguy

5

성능 및 메모리 사용량의 문제이며, 많이 사용해야하는 경우 컴파일 된 패턴을 컴파일하고 유지합니다. 정규식의 일반적인 사용의 유효성을 검사 사용자이다 입력 (형식) , 또한 사용자에 대한 출력 데이터 형식을 컴파일 된 패턴을 저장, 이러한 클래스에서, 그들은 일반적으로 많이 불리는으로 매우 논리적 인 것 같다.

아래는 실제로 많이 호출되는 샘플 유효성 검사기입니다. :)

public class AmountValidator {
    //Accept 123 - 123,456 - 123,345.34
    private static final String AMOUNT_REGEX="\\d{1,3}(,\\d{3})*(\\.\\d{1,4})?|\\.\\d{1,4}";
    //Compile and save the pattern  
    private static final Pattern AMOUNT_PATTERN = Pattern.compile(AMOUNT_REGEX);


    public boolean validate(String amount){

         if (!AMOUNT_PATTERN.matcher(amount).matches()) {
            return false;
         }    
        return true;
    }    
}

@Alan Moore가 언급했듯이 코드에 재사용 가능한 정규식이있는 경우 (예 : 루프 전) 재사용을 위해 패턴을 컴파일하고 저장해야합니다.


2

Pattern.compile()정규식을 여러 번 재사용 할 수 있습니다 (스레드 안전). 성능상의 이점은 상당히 클 수 있습니다.

빠른 벤치 마크를 수행했습니다.

    @Test
    public void recompile() {
        var before = Instant.now();
        for (int i = 0; i < 1_000_000; i++) {
            Pattern.compile("ab").matcher("abcde").matches();
        }
        System.out.println("recompile " + Duration.between(before, Instant.now()));
    }

    @Test
    public void compileOnce() {
        var pattern = Pattern.compile("ab");
        var before = Instant.now();
        for (int i = 0; i < 1_000_000; i++) {
            pattern.matcher("abcde").matches();
        }
        System.out.println("compile once " + Duration.between(before, Instant.now()));
    }

compileOnce는 3 배에서 4 배까지 더 빠릅니다 . 나는 그것이 정규식 자체에 크게 의존한다고 생각하지만 자주 사용되는 정규식의 경우static Pattern pattern = Pattern.compile(...)


0

정규식을 미리 컴파일하면 속도가 빨라집니다. Matcher를 재사용하면 약간의 속도가 향상됩니다. 메서드가 자주 호출되면 루프 내에서 호출된다고하면 전체 성능이 확실히 올라갑니다.


0

'Pattern.compile'과 유사하게 'RECompiler.compile'[com.sun.org.apache.regexp.internal]이 있습니다. 여기서 :
1. 패턴 [az]에 대한 컴파일 된 코드에는 'az'가 있습니다
. 패턴 [0-9]에는 '09'가 있습니다.
3. 패턴 [abc]에 대한 컴파일 된 코드에는 'aabbcc'가 있습니다.

따라서 컴파일 된 코드는 여러 사례를 일반화하는 좋은 방법입니다. 따라서 다른 코드 처리 상황 1,2 및 3 대신. 문제는 컴파일 된 코드에서 현재 및 다음 요소의 ascii와 비교하는 것으로 줄어 듭니다. 따라서
a. a와 z 사이에 ascii가있는 것은 a와 z 사이
입니다. b. 'a와 a 사이에 ASCII가있는 것은 분명히'a '입니다.


0

패턴 클래스는 정규식 엔진의 진입 점으로 Pattern.matches () 및 Pattern.comiple ()을 통해 사용할 수 있습니다. #이 둘의 차이점. match () -텍스트 (문자열)가 주어진 정규식과 일치하는지 빠르게 확인하기 위해 comiple ()-Pattern 의 참조를 만듭니다. 따라서 여러 텍스트에 대해 정규식을 일치시키기 위해 여러 번 사용할 수 있습니다.

참고로 :

public static void main(String[] args) {
     //single time uses
     String text="The Moon is far away from the Earth";
     String pattern = ".*is.*";
     boolean matches=Pattern.matches(pattern,text);
     System.out.println("Matches::"+matches);

    //multiple time uses
     Pattern p= Pattern.compile("ab");
     Matcher  m=p.matcher("abaaaba");
     while(m.find()) {
         System.out.println(m.start()+ " ");
     }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.