java.util.regex - tầm quan trọng của Pattern.compile ()?


118

Tầm quan trọng của Pattern.compile()phương pháp là gì?
Tại sao tôi cần biên dịch chuỗi regex trước khi lấy Matcherđối tượng?

Ví dụ :

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

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

2
Chà, tầm quan trọng gần như KHÔNG CÓ nếu việc triển khai (như trong JDK 1.7) chỉ là SHORTCUT đối với Mẫu mới (regex, 0); Điều đó nói lên rằng, tầm quan trọng THỰC SỰ không phải là bản thân phương thức tĩnh, mà là việc tạo và trả về một Mẫu mới có thể được lưu lại để sử dụng sau này. Có thể có những cách triển khai khác trong đó phương thức static lấy một tuyến mới và lưu vào bộ nhớ cache các đối tượng Pattern, và đó sẽ là một trường hợp thực sự về tầm quan trọng của Pattern.compile ()!
marcolopes

Các câu trả lời làm nổi bật tầm quan trọng của việc tách các lớp mẫu và kết hợp (có thể là những gì câu hỏi đặt ra), nhưng không ai trả lời được tại sao chúng ta không thể sử dụng một hàm tạo new Pattern(regex)thay vì một hàm biên dịch tĩnh. bình luận marcolopes là tại chỗ.
kon psych

Câu trả lời:


144

Các compile()phương pháp được gọi là luôn luôn tại một số điểm; đó là cách duy nhất để tạo một đối tượng Pattern. Vì vậy, câu hỏi thực sự là, tại sao bạn nên gọi nó một cách rõ ràng ? Một lý do là bạn cần tham chiếu đến đối tượng Matcher để bạn có thể sử dụng các phương thức của nó, chẳng hạn như group(int)lấy nội dung của các nhóm bắt. Cách duy nhất để có được đối tượng Matcher là thông qua matcher()phương thức của đối tượng Pattern và cách duy nhất để có được đối tượng Pattern là thông qua compile()phương thức. Sau đó, có find()phương thức, không giống như matches(), không bị trùng lặp trong các lớp Chuỗi hoặc Mẫu.

Lý do khác là tránh tạo lặp đi lặp lại cùng một đối tượng Pattern. Mỗi khi bạn sử dụng một trong các phương thức được hỗ trợ bởi regex trong Chuỗi (hoặc matches()phương thức tĩnh trong Mẫu), nó sẽ tạo ra một Mẫu mới và một Matcher mới. Vì vậy, đoạn mã này:

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

... chính xác tương đương với điều này:

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

Rõ ràng, đó là làm rất nhiều công việc không cần thiết. Trên thực tế, có thể dễ dàng mất nhiều thời gian để biên dịch regex và khởi tạo đối tượng Pattern, hơn là thực hiện một khớp thực tế. Vì vậy, nó thường là hợp lý để kéo bước đó ra khỏi vòng lặp. Bạn cũng có thể tạo Matcher trước thời hạn, mặc dù chúng gần như không quá đắt:

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

Nếu bạn đã quen thuộc với .NET regexes, bạn có thể tự hỏi liệu compile()phương thức của Java có liên quan đến công cụ RegexOptions.Compiledsửa đổi của .NET hay không ; câu trả lời là không. Pattern.compile()Phương thức của Java chỉ tương đương với phương thức khởi tạo Regex của .NET. Khi bạn chỉ định Compiledtùy chọn:

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

... nó biên dịch regex trực tiếp thành mã byte CIL, cho phép nó hoạt động nhanh hơn nhiều, nhưng với chi phí đáng kể trong xử lý phía trước và sử dụng bộ nhớ - hãy nghĩ về nó như steroid cho regex. Java không có tương đương; không có sự khác biệt giữa Mẫu được tạo đằng sau hậu trường String#matches(String)và Mẫu do bạn tạo ra một cách rõ ràng Pattern#compile(String).

(CHỈNH SỬA: Ban đầu tôi đã nói rằng tất cả các đối tượng .NET Regex đều được lưu trong bộ nhớ cache, điều này không chính xác. Kể từ .NET 2.0, bộ nhớ đệm tự động chỉ xảy ra với các phương thức tĩnh như Regex.Matches(), không phải khi bạn gọi trực tiếp một phương thức tạo Regex. Ref )


1
Tuy nhiên, điều này không giải thích tầm quan trọng của phương thức TRIVIAL như vậy trên lớp Pattern! Tôi luôn giả định rằng phương thức tĩnh Pattern.compile không chỉ đơn giản là một SHORTCUT thành Mẫu mới (regex, 0); Tôi đã mong đợi một CACHE của các mẫu biên dịch ... tôi đã nhầm. Có lẽ việc tạo cache sẽ tốn kém hơn việc tạo các mẫu mới ??!
marcolopes

9
Xin lưu ý rằng lớp Matcher không an toàn cho chuỗi và không nên được chia sẻ giữa các chuỗi. Mặt khác, Pattern.compile () là.
gswierczynski

1
TLDR; "... [Pattern.compile (...)] biên dịch regex trực tiếp thành mã byte CIL, cho phép nó hoạt động nhanh hơn nhiều, nhưng với chi phí đáng kể trong xử lý trước và sử dụng bộ nhớ"
sean.boyer

3
Mặc dù đúng là Matcher gần như không đắt như Pattern. Compile, tôi đã thực hiện một số chỉ số trong một kịch bản mà hàng nghìn trận đấu regex đang diễn ra và có một khoản tiết kiệm bổ sung, rất đáng kể bằng cách tạo Matcher trước và sử dụng lại nó thông qua matcher .cài lại(). Việc tránh tạo các đối tượng mới trong heap trong các phương thức được gọi là hàng nghìn lần thường nhẹ hơn nhiều đối với CPU, bộ nhớ và do đó GC.
Volksman

@Volksman đó không phải là lời khuyên chung an toàn vì các đối tượng Matcher không an toàn. Nó cũng không liên quan đến câu hỏi. Nhưng có, bạn có thể resetlà một đối tượng Matcher chỉ được sử dụng bởi một luồng tại một thời điểm để giảm phân bổ.
AndrewF

40

Biên dịch phân tích cú pháp biểu thức chính quy và xây dựng một biểu diễn trong bộ nhớ . Chi phí để biên dịch là đáng kể so với một trận đấu. Nếu bạn đang sử dụng một mẫu liên tục, nó sẽ đạt được một số hiệu suất để lưu vào bộ nhớ cache của mẫu đã biên dịch.


7
Thêm vào đó bạn có thể chỉ định cờ như case_insensitive, dot_all vv quá trình biên dịch, bằng cách thông qua trong một lá cờ thêm tham số
Sam Barnum

17

Khi bạn biên dịch PatternJava thực hiện một số tính toán để giúp việc tìm kiếm các kết quả phù hợp Stringnhanh hơn. (Xây dựng biểu diễn trong bộ nhớ của regex)

Nếu bạn định sử dụng lại Patternnhiều lần, bạn sẽ thấy hiệu suất tăng lên đáng kể so với việc tạo mới Patternmỗi lần.

Trong trường hợp chỉ sử dụng Mẫu một lần, bước biên dịch có vẻ giống như một dòng mã bổ sung, nhưng trên thực tế, nó có thể rất hữu ích trong trường hợp chung.


5
Tất nhiên bạn có thể viết tất cả trong một dòng Matcher matched = Pattern.compile(regex).matcher(text);. Điều này có lợi thế hơn so với việc giới thiệu một phương pháp duy nhất: các đối số được đặt tên một cách hiệu quả và rõ ràng là làm thế nào để xác định yếu tố Patternđể có hiệu suất tốt hơn (hoặc phân chia giữa các phương thức).
Tom Hawtin - tackline

1
Có vẻ như bạn biết rất nhiều về Java. Họ nên tuyển dụng bạn để làm việc cho họ ...
jjnguy

5

Vấn đề là hiệu suất và sử dụng bộ nhớ, hãy biên dịch và giữ nguyên mẫu tuân thủ nếu bạn cần sử dụng nhiều. Một cách sử dụng điển hình của regex là để xác thực đầu vào của người dùng (định dạng) và cũng định dạng dữ liệu đầu ra cho người dùng , trong các lớp này, lưu mẫu tuân thủ, có vẻ khá hợp lý như chúng thường gọi là nhiều.

Dưới đây là một trình xác nhận mẫu, thực sự được gọi là rất nhiều :)

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;
    }    
}

Như đã đề cập bởi @Alan Moore, nếu bạn có regex có thể tái sử dụng trong mã của mình, (trước một vòng lặp chẳng hạn), bạn phải biên dịch và lưu mẫu để sử dụng lại.


2

Pattern.compile()cho phép sử dụng lại nhiều lần một regex (nó là threadsafe). Lợi ích về hiệu suất có thể khá đáng kể.

Tôi đã làm một điểm chuẩn nhanh:

    @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 nhanh hơn từ 3x đến 4x . Tôi đoán nó phụ thuộc nhiều vào bản thân regex nhưng đối với một regex thường được sử dụng, tôi chọnstatic Pattern pattern = Pattern.compile(...)


0

Biên dịch trước regex làm tăng tốc độ. Sử dụng lại Matcher sẽ giúp bạn tăng tốc một chút. Nếu phương thức được gọi thường xuyên nói rằng được gọi trong một vòng lặp, hiệu suất tổng thể chắc chắn sẽ tăng lên.


0

Tương tự như 'Pattern.compile' có 'RECompiler.compile' [từ com.sun.org.apache.regexp.internal] trong đó:
1. mã biên dịch cho mẫu [az] có 'az' trong đó
2. mã biên dịch cho pattern [0-9] có '09' trong đó
3. mã đã biên dịch cho pattern [abc] có 'aabbcc' trong đó.

Do đó, mã đã biên dịch là một cách tuyệt vời để tổng quát hóa nhiều trường hợp. Do đó, thay vì có các mã xử lý tình huống 1,2 và 3 khác nhau. Vấn đề giảm xuống so sánh với ascii của phần tử hiện tại và tiếp theo trong mã đã biên dịch, do đó các cặp. Như vậy
a. bất cứ thứ gì có ascii giữa a và z nằm giữa a và z
b. bất cứ thứ gì có ascii giữa 'a và a chắc chắn là' a '


0

Lớp mẫu là điểm vào của công cụ regex, bạn có thể sử dụng nó thông qua Pattern.matches () và Pattern.comiple (). # Sự khác biệt giữa hai điều này. match () - để nhanh chóng kiểm tra xem một văn bản (Chuỗi) có khớp với một biểu thức chính quy đã cho comiple () - hãy tạo tham chiếu của Pattern. Vì vậy, có thể sử dụng nhiều lần để đối sánh biểu thức chính quy với nhiều văn bản.

Để tham khảo:

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()+ " ");
     }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.