java.util.regex - ความสำคัญของ Pattern.compile ()?


118

ความสำคัญของPattern.compile()วิธีการคืออะไร?
เหตุใดฉันจึงต้องคอมไพล์สตริง regex ก่อนที่จะรับ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) เป็นเพียงทางลัดไปสู่รูปแบบใหม่ (regex, 0); ที่กล่าวว่าความสำคัญจริงไม่ใช่วิธีการคงที่ แต่เป็นการสร้างและส่งคืนรูปแบบใหม่ที่สามารถบันทึกไว้เพื่อใช้ในภายหลัง อาจมีการใช้งานอื่น ๆ ที่วิธีการแบบคงที่ใช้เส้นทางใหม่และแคชวัตถุ Pattern และนั่นอาจเป็นกรณีที่แท้จริงของความสำคัญของ Pattern.compile ()!
marcolopes

คำตอบเน้นความสำคัญของการแยกรูปแบบและคลาสที่ตรงกัน (ซึ่งอาจเป็นสิ่งที่คำถามถาม) แต่ไม่มีใครตอบได้ว่าทำไมเราไม่สามารถใช้ตัวสร้างnew Pattern(regex)แทนฟังก์ชันคอมไพล์แบบคงที่ได้ ความคิดเห็นของ marcolopes อยู่ในจุด
kon psych

คำตอบ:


144

compile()วิธีการที่มักจะเรียกว่าในบางจุด; เป็นวิธีเดียวในการสร้างวัตถุ Pattern คำถามก็คือทำไมคุณถึงเรียกมันอย่างโจ่งแจ้ง ? เหตุผลประการหนึ่งคือคุณต้องการการอ้างอิงถึงวัตถุ Matcher เพื่อให้คุณสามารถใช้วิธีการต่างๆเช่นgroup(int)ดึงเนื้อหาของการจับกลุ่ม วิธีเดียวที่จะได้รับ ahold ของวัตถุ Matcher คือโดยใช้matcher()วิธีการของวัตถุ Pattern และวิธีเดียวที่จะได้รับ ahold ของวัตถุ Pattern คือผ่านcompile()วิธีการ จากนั้นมีfind()วิธีการที่ไม่เหมือนmatches()กันคือไม่ซ้ำกันในคลาส String หรือ Pattern

อีกเหตุผลหนึ่งคือหลีกเลี่ยงการสร้างวัตถุ Pattern เดียวกันซ้ำแล้วซ้ำเล่า ทุกครั้งที่คุณใช้หนึ่งในวิธีที่ขับเคลื่อนด้วย regex ใน 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();
    }
}

เห็นได้ชัดว่านั่นเป็นการทำงานที่ไม่จำเป็นมากมาย ในความเป็นจริงอาจใช้เวลาในการคอมไพล์ regex และสร้างอินสแตนซ์อ็อบเจ็กต์ Pattern นานกว่าที่จะทำการจับคู่จริงได้อย่างง่ายดาย ดังนั้นจึงเป็นเรื่องที่สมเหตุสมผลที่จะดึงขั้นตอนนั้นออกจากวง คุณสามารถสร้าง Matcher ล่วงหน้าได้เช่นกันแม้ว่าจะไม่แพงมากนัก:

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

หากคุณคุ้นเคยกับ regexes .NET คุณอาจสงสัยว่าcompile()เมธอดของ Java เกี่ยวข้องกับRegexOptions.Compiledตัวปรับแต่งของ. NET หรือไม่ คำตอบคือไม่ Pattern.compile()วิธีการของ Java นั้นเทียบเท่ากับตัวสร้าง Regex ของ. NET เท่านั้น เมื่อคุณระบุCompiledตัวเลือก:

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

... มันรวบรวม regex โดยตรงกับโค้ด CIL byte ทำให้ทำงานได้เร็วขึ้นมาก แต่มีต้นทุนสูงในการประมวลผลล่วงหน้าและการใช้หน่วยความจำ - คิดว่ามันเป็นสเตียรอยด์สำหรับ regexes Java ไม่มีทางเทียบเท่า มีความแตกต่างระหว่างรูปแบบที่สร้างขึ้นอยู่เบื้องหลังโดยไม่ได้และคนที่คุณสร้างอย่างชัดเจนด้วยString#matches(String)Pattern#compile(String)

(แก้ไข: แต่เดิมฉันบอกว่าอ็อบเจ็กต์. NET Regex ทั้งหมดถูกแคชซึ่งไม่ถูกต้องตั้งแต่. NET 2.0 การแคชอัตโนมัติจะเกิดขึ้นเฉพาะกับวิธีการแบบคงที่เช่นRegex.Matches()ไม่ใช่เมื่อคุณเรียกตัวสร้าง Regex โดยตรงอ้างอิง )


1
แต่นี่ไม่ได้อธิบายถึงความสำคัญของวิธี TRIVIAL ในคลาส Pattern! ฉันคิดเสมอว่าวิธีการแบบคงที่ Pattern.compile นั้นเป็นมากกว่า SHORTCUT แบบธรรมดาไปยัง Pattern ใหม่ (regex, 0); ฉันคาดหวังว่าจะมี CACHE ของรูปแบบที่คอมไพล์ ... ฉันคิดผิด บางทีการสร้าง Cache อาจแพงกว่าการสร้าง Pattern ใหม่ ??!
marcolopes

9
โปรดทราบว่าคลาส Matcher ไม่ปลอดภัยต่อเธรดและไม่ควรแชร์ข้ามเธรด ในทางกลับกัน Pattern.compile () คือ
gswierczynski

1
TLDR; "... [Pattern.compile (... )] รวบรวม regex โดยตรงกับโค้ด CIL byte ทำให้ทำงานได้เร็วขึ้นมาก แต่เสียค่าใช้จ่ายมากในการประมวลผลล่วงหน้าและการใช้หน่วยความจำ"
sean.boyer

3
แม้ว่า Matchers จะไม่แพงเท่า Pattern.compile แต่ฉันได้ใช้เมตริกบางอย่างในสถานการณ์ที่มีการจับคู่ regex หลายพันรายการเกิดขึ้นและมีการประหยัดเพิ่มเติมที่สำคัญมากโดยการสร้าง Matcher ล่วงหน้าและนำกลับมาใช้ใหม่ผ่าน matcher .reset () การหลีกเลี่ยงการสร้างวัตถุใหม่ในฮีปด้วยวิธีการที่เรียกว่าหลายพันครั้งมักจะเบากว่ามากใน CPU หน่วยความจำและ GC
Volksman

@Volksman ที่ไม่ปลอดภัยคำแนะนำทั่วไปเนื่องจากวัตถุ Matcher ไม่ปลอดภัยเธรด นอกจากนี้ยังไม่เกี่ยวข้องกับคำถาม แต่ใช่คุณสามารถresetMatcher object ที่เคยใช้ทีละเธรดเพื่อลดการจัดสรร
AndrewF

40

คอมไพล์แยกวิเคราะห์การแสดงออกปกติและสร้างตัวแทนในหน่วยความจำ ค่าใช้จ่ายในการรวบรวมมีความสำคัญเมื่อเทียบกับการจับคู่ หากคุณใช้รูปแบบซ้ำ ๆมันจะได้รับประสิทธิภาพในการแคชรูปแบบที่คอมไพล์


7
นอกจากนี้คุณสามารถระบุแฟล็กเช่น case_insensitive, dot_all และอื่น ๆ ในระหว่างการคอมไพล์โดยส่งผ่านพารามิเตอร์แฟล็กพิเศษ
Sam Barnum

17

เมื่อคุณคอมไพล์PatternJava จะทำการคำนวณเพื่อค้นหารายการที่ตรงกันในString s เร็วขึ้น (สร้างตัวแทนในหน่วยความจำของ regex)

หากคุณจะใช้ซ้ำPatternหลาย ๆ ครั้งคุณจะเห็นประสิทธิภาพเพิ่มขึ้นอย่างมากจากการสร้างไฟล์Patternทุกครั้ง

ในกรณีที่ใช้ Pattern เพียงครั้งเดียวขั้นตอนการคอมไพล์จะดูเหมือนเป็นบรรทัดของโค้ดเพิ่มเติม แต่อันที่จริงมันมีประโยชน์มากในกรณีทั่วไป


5
แน่นอนคุณสามารถเขียนทั้งหมดในบรรทัดMatcher matched = Pattern.compile(regex).matcher(text);เดียว มีข้อดีมากกว่าการแนะนำวิธีการเดียว: อาร์กิวเมนต์ได้รับการตั้งชื่ออย่างมีประสิทธิภาพและเห็นได้ชัดว่าจะแยกตัวประกอบPatternเพื่อประสิทธิภาพที่ดีขึ้นได้อย่างไร (หรือแยกตามวิธีการต่างๆ)
Tom Hawtin - แทคไลน์

1
ดูเหมือนว่าคุณจะรู้มากเกี่ยวกับ Java อยู่เสมอ พวกเขาควรจ้างคุณทำงานให้พวกเขา ...
jjnguy

5

เป็นเรื่องของประสิทธิภาพและการใช้งานหน่วยความจำรวบรวมและรักษารูปแบบที่เป็นไปตามข้อกำหนดหากคุณต้องการใช้เป็นจำนวนมาก การใช้ regex โดยทั่วไปคือการป้อนข้อมูลของผู้ใช้(รูปแบบ) ที่ผ่านการตรวจสอบและจัดรูปแบบข้อมูลเอาต์พุตสำหรับผู้ใช้ด้วยในคลาสเหล่านี้การบันทึกรูปแบบที่เป็นไปตามนั้นดูเหมือนจะค่อนข้างมีเหตุผลเนื่องจากมักเรียกว่าจำนวนมาก

ด้านล่างนี้คือตัวตรวจสอบตัวอย่างซึ่งเรียกได้ว่าเยอะจริงๆ :)

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 หากคุณมี regex ที่ใช้ซ้ำได้ในโค้ดของคุณ (ก่อนการวนซ้ำเป็นต้น) คุณต้องรวบรวมและบันทึกรูปแบบเพื่อนำมาใช้ใหม่


2

Pattern.compile()อนุญาตให้ใช้ regex ซ้ำได้หลายครั้ง (เป็น threadsafe) ประโยชน์ด้านประสิทธิภาพมีความสำคัญมาก

ฉันทำเกณฑ์มาตรฐานอย่างรวดเร็ว:

    @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 ระหว่าง3x และ 4x เร็วขึ้น ฉันเดาว่ามันขึ้นอยู่กับ regex เป็นอย่างมาก แต่สำหรับ regex ที่มักใช้ฉันจะไปหาไฟล์static Pattern pattern = Pattern.compile(...)


0

การคอมไพล์ล่วงหน้า regex จะเพิ่มความเร็ว การใช้ Matcher ซ้ำจะช่วยให้คุณเพิ่มความเร็วได้อีกเล็กน้อย ถ้าเมธอดถูกเรียกบ่อย ๆ ว่าถูกเรียกภายในลูปประสิทธิภาพโดยรวมจะเพิ่มขึ้นอย่างแน่นอน


0

คล้ายกับ 'Pattern.compile' มี 'RECompiler.compile' [จาก com.sun.org.apache.regexp.internal] โดยที่:
1. คอมไพล์โค้ดสำหรับ pattern [az] มี 'az' อยู่
2. โค้ดที่คอมไพล์สำหรับ รูปแบบ [0-9] มี '09' อยู่ในนั้น
3. โค้ดที่คอมไพล์แล้วสำหรับ pattern [abc] มี 'aabbcc' อยู่ในนั้น

โค้ดที่คอมไพล์แล้วเป็นวิธีที่ยอดเยี่ยมในการสรุปกรณีต่างๆ ดังนั้นแทนที่จะมีสถานการณ์การจัดการโค้ดที่แตกต่างกัน 1,2 และ 3 ปัญหาจะลดลงเมื่อเปรียบเทียบกับ ascii ขององค์ประกอบปัจจุบันและองค์ประกอบถัดไปในโค้ดที่คอมไพล์ดังนั้นคู่ ดังนั้น
อะไรก็ตามที่มี ascii ระหว่าง a และ z อยู่ระหว่าง a และ z
b อะไรก็ได้ที่มี ascii ระหว่าง 'a และ a แน่นอน' a '


0

คลาส Pattern เป็นจุดเริ่มต้นของเอนจิ้น regex คุณสามารถใช้ผ่าน Pattern.matches () และ Pattern.comiple () # ความแตกต่างระหว่างสองสิ่งนี้ match () - เพื่อตรวจสอบอย่างรวดเร็วว่าข้อความ (String) ตรงกับนิพจน์ทั่วไปที่กำหนด 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.