ลดความซับซ้อนของงานการเขียนโปรแกรมทั่วไปด้วย Regex API
ในส่วนที่ 1 และ 2 ของบทความนี้ คุณได้รู้จักกับนิพจน์ทั่วไปและ Regex API คุณได้เรียนรู้เกี่ยวกับชั้นเรียนPattern
และได้ดูตัวอย่างที่สาธิตการสร้างนิพจน์ทั่วไป ตั้งแต่การจับคู่รูปแบบอย่างง่ายโดยใช้สตริงตามตัวอักษรไปจนถึงการจับคู่ที่ซับซ้อนมากขึ้นโดยใช้ช่วง ตัวจับคู่ขอบเขต และตัวระบุปริมาณ ในส่วนนี้และส่วน ต่อๆ ไป เราจะพิจารณาประเด็นต่างๆ ที่ไม่ครอบคลุมในส่วนแรก เราจะศึกษาวิธีการที่เกี่ยวข้องของชั้นเรียนPattern
และ นอกจากนี้คุณยังจะได้เรียนรู้ยูทิลิตี้สองตัวที่ใช้นิพจน์ทั่วไปเพื่อทำให้ปัญหาการเขียนโปรแกรมทั่วไปง่ายขึ้น อันแรกแยกความคิดเห็นจากโค้ดสำหรับเอกสารประกอบ ส่วนที่สองคือไลบรารีของโค้ดที่นำมาใช้ซ้ำได้ ซึ่งออกแบบมาเพื่อดำเนินการวิเคราะห์คำศัพท์ ซึ่งเป็นองค์ประกอบสำคัญของแอสเซมเบลอร์ คอมไพเลอร์ และซอฟต์แวร์ที่คล้ายกัน Matcher
PatternSyntaxException
กำลังดาวน์โหลดซอร์สโค้ด
คุณสามารถรับซอร์สโค้ดทั้งหมด (สร้างโดย Jeff Friesen สำหรับ JavaWorld) สำหรับแอปพลิเคชันสาธิตในบทความนี้ได้จากที่นี่การเรียนรู้ Regex API
Pattern
และMatcher
เป็นPatternSyntaxException
สามคลาสที่ประกอบกันเป็น Regex API แต่ละวิธีมีวิธีการที่ช่วยให้คุณใช้นิพจน์ทั่วไปในโค้ดของคุณได้
วิธีการเรียนรูปแบบ
อินสแตนซ์ของคลาสPattern
คือนิพจน์ทั่วไปที่คอมไพล์แล้ว หรือที่เรียกว่ารูปแบบ นิพจน์ทั่วไปได้รับการคอมไพล์เพื่อปรับปรุงประสิทธิภาพของการดำเนินการจับคู่รูปแบบ วิธีการคงที่ต่อไปนี้รองรับการคอมไพล์
Pattern compile(String regex)
รวบรวมเนื้อหาregex
เป็นตัวแทนระดับกลางซึ่งจัดเก็บไว้ในไฟล์Pattern
. เมธอดนี้จะส่งคืนการอ้างอิงไปยังออบเจ็กต์หากสำเร็จ หรือส่งข้อยกเว้นPatternSyntaxException
หากตรวจพบไวยากรณ์ของนิพจน์ทั่วไปที่ไม่ถูกต้อง อ็อบเจ็กต์ใดๆ ของคลาสMatcher
ที่ใช้Pattern
หรือส่งคืนจากอ็อบเจ็กต์นี้จะใช้การตั้งค่าเริ่มต้น เช่น การค้นหาแบบคำนึงถึงขนาดตัวพิมพ์ ตามตัวอย่าง ข้อมูลโค้ดPattern p = Pattern.compile("(?m)^\\.");
จะสร้างออบเจ็กต์Pattern
ที่จัดเก็บการแสดงนิพจน์ทั่วไปที่คอมไพล์แล้วเพื่อจับคู่สตริงที่ขึ้นต้นด้วยอักขระจุดPattern compile(String regex, int flags)
แก้ปัญหาเดียวกันกับPattern compile(String regex)
แต่คำนึงถึงflags
: ชุดของค่าคงที่บิตสำหรับแฟล็กบิตของประเภท OR คลาสPattern
ประกาศค่าคงที่ที่CANON_EQ, CASE_INSENSITIVE, COMMENTS, DOTALL, LITERAL, MULTILINE, UNICODE_CASE, UNICODE_CHARACTER_CLASS и UNIX_LINES
สามารถรวมกันได้โดยใช้ระดับบิต OR (เช่นCASE_INSENSITIVE | DOTALL
) และส่งผ่านเป็นอาร์กิวเมนต์flags
ด้วยข้อยกเว้นของ
CANON_EQ, LITERAL и UNICODE_CHARACTER_CLASS
ค่าคงที่เหล่านี้เป็นทางเลือกแทนนิพจน์แฟล็กแบบซ้อนที่แสดงในส่วนที่ 1 ถ้าพบค่าคงที่แฟล็กอื่นนอกเหนือจากที่กำหนดไว้ในคลาสPattern
วิธีการPattern compile(String regex, int flags)
จะส่งข้อjava.lang.IllegalArgumentException
ยกเว้น ตัวอย่างPattern p = Pattern.compile("^\\.", Pattern.MULTILINE);
เช่น เทียบเท่ากับตัวอย่างก่อนหน้า โดยมีค่าคงที่Pattern.MULTILINE
และนิพจน์แฟล็กที่ซ้อน(?m)
กันทำสิ่งเดียวกัน
Pattern
พร้อมกับแฟล็กที่ใช้ เมื่อต้องการทำเช่นนี้ คุณสามารถเรียกวิธีการต่อไปนี้:
String pattern()
ส่งคืนสตริงนิพจน์ทั่วไปดั้งเดิมที่คอมไพล์เป็นไฟล์Pattern
.int flags()
Pattern
ส่งคืนแฟล็ กของวัตถุ
Pattern
โดยทั่วไปจะใช้เพื่อรับวัตถุMatcher
เพื่อดำเนินการจับคู่รูปแบบ วิธีการMatcher matcher(Charsequence input)
สร้างวัตถุMatcher
ที่ค้นหาข้อความinput
เพื่อให้ตรงกับรูปแบบของPattern
วัตถุ เมื่อเรียกก็จะส่งกลับการอ้างอิงไปยังวัตถุMatcher
นี้ ตัวอย่างเช่น คำสั่งส่ง Matcher m = p.matcher(args[1]);
คืนMatcher
อ็อบเจ็กต์Pattern
ที่อ้างอิงโดยตัวแปรp
การค้นหาเพียงครั้งเดียว |
---|
วิธีstatic boolean matches(String regex, CharSequence input) การเรียนPattern ช่วยให้คุณประหยัดในการสร้างวัตถุPattern และMatcher การค้นหาครั้งเดียวโดยใช้เทมเพลต เมธอดนี้จะคืนค่าเป็นจริงหากinput รูปแบบตรงกันregex มิฉะนั้นจะส่งคืนค่าเท็จ หากนิพจน์ทั่วไปมีข้อผิดพลาดทางไวยากรณ์ เมธอดจะแสดงข้อPatternSyntaxException ยกเว้น ตัวอย่างเช่นSystem.out.println(Pattern.matches("[a-z[\\s]]*", "all lowercase letters and whitespace only")); พิมพ์true เพื่อยืนยันว่าวลีall lowercase letters and whitespace only มีเพียงช่องว่างและอักขระตัวพิมพ์เล็ก |

การแยกข้อความ
นักพัฒนาส่วนใหญ่มีโค้ดที่เขียนอย่างน้อยหนึ่งครั้งเพื่อแบ่งข้อความที่ป้อนออกเป็นส่วนต่างๆ เช่น การแปลงบัญชีพนักงานแบบข้อความเป็นชุดช่อง คลาสนี้Pattern
ช่วยให้แก้ปัญหางานที่น่าเบื่อนี้ได้สะดวกยิ่งขึ้นโดยใช้วิธีแยกข้อความสองวิธี:
-
วิธีการ
String[] split(CharSequence text, int limit)
แยกtext
ตามที่พบตรงกับรูปแบบวัตถุPattern
และส่งกลับผลลัพธ์ในอาร์เรย์ แต่ละองค์ประกอบอาร์เรย์ระบุลำดับข้อความที่แยกจากลำดับถัดไปโดยส่วนข้อความที่ตรงกับรูปแบบ (หรือส่วนท้ายของข้อความ) องค์ประกอบของอาร์เรย์อยู่ในลำดับเดียวกับที่ปรากฏtext
ในในวิธีนี้ จำนวนองค์ประกอบอาร์เรย์จะขึ้นอยู่กับพารามิเตอร์
limit
ซึ่งควบคุมจำนวนรายการที่ตรงกันที่จะพบด้วย- ค่าบวกจะค้นหาไม่เกินค่า
limit-1
ที่ตรงกัน และความยาวของอาร์เรย์ไม่เกินlimit
องค์ประกอบ - หากค่าเป็นลบ ระบบจะค้นหารายการที่ตรงกันที่เป็นไปได้ทั้งหมด และความยาวของอาร์เรย์สามารถกำหนดเองได้
- หากค่าเป็นศูนย์ ระบบจะค้นหารายการที่ตรงกันที่เป็นไปได้ทั้งหมด ความยาวของอาเรย์สามารถกำหนดเองได้ และบรรทัดว่างที่ส่วนท้ายจะถูกละทิ้ง
- ค่าบวกจะค้นหาไม่เกินค่า
- วิธีการ
String[] split(CharSequence text)
เรียกวิธีการก่อนหน้านี้ด้วย 0 เป็นอาร์กิวเมนต์จำกัดและส่งกลับผลลัพธ์ของการเรียก
split(CharSequence text)
การแก้ไขปัญหาการแบ่งบัญชีพนักงานออกเป็นช่องชื่อ อายุ ที่อยู่ทางไปรษณีย์ และเงินเดือน:
Pattern p = Pattern.compile(",\\s");
String[] fields = p.split("John Doe, 47, Hillsboro Road, 32000");
for (int i = 0; i < fields.length; i++)
System.out.println(fields[i]);
โค้ดด้านบนอธิบายนิพจน์ทั่วไปเพื่อค้นหาอักขระลูกน้ำตามด้วยอักขระเว้นวรรคตัวเดียวทันที นี่คือผลลัพธ์ของการดำเนินการ:
John Doe
47
Hillsboro Road
32000
เพรดิเคตเทมเพลตและ Streams API
ใน Java 8Pattern
วิธีการปรากฏ ในคลาส . วิธีการนี้จะสร้างเพรดิเคต (ฟังก์ชันที่มีค่าบูลีน) ที่ใช้เพื่อให้ตรงกับรูปแบบ การใช้วิธีนี้จะแสดงอยู่ในข้อมูลโค้ดต่อไปนี้: Predicate
asPredicate()
List progLangs = Arrays.asList("apl", "basic", "c", "c++", "c#", "cobol", "java", "javascript", "perl", "python", "scala");
Pattern p = Pattern.compile("^c");
progLangs.stream().filter(p.asPredicate()).forEach(System.out::println);
โค้ดนี้จะสร้างรายการชื่อภาษาการเขียนโปรแกรม จากนั้นรวบรวมรูปแบบเพื่อค้นหาชื่อทั้งหมดที่ขึ้นต้นด้วยตัวc
อักษร บรรทัดสุดท้ายของโค้ดด้านบนใช้รับสตรีมข้อมูลแบบอนุกรมโดยมีรายการนี้เป็นแหล่งที่มา โดยจะตั้งค่าตัวกรองโดยใช้ฟังก์ชันบูลีนasPredicate()
ที่คืนค่าเป็นจริงเมื่อชื่อขึ้นต้นด้วยตัวอักษรc
และวนซ้ำผ่านสตรีม โดยพิมพ์ชื่อที่ตรงกันไปยังเอาต์พุตมาตรฐาน บรรทัดสุดท้ายนี้เทียบเท่ากับการวนซ้ำปกติต่อไปนี้ ซึ่งคุ้นเคยจากแอปพลิเคชัน RegexDemo จากส่วนที่ 1:
for (String progLang: progLangs)
if (p.matcher(progLang).find())
System.out.println(progLang);
วิธีการคลาส Matcher
อินสแตนซ์ของคลาสMatcher
อธิบายกลไกในการดำเนินการจับคู่รูปแบบตามลำดับอักขระโดยการตีความนิพจน์ทั่วไปที่คอมไพล์แล้วของPattern
คลาส อ็อบเจ็กต์ของคลาสMatcher
สนับสนุนการดำเนินการค้นหารูปแบบประเภทต่างๆ:
-
วิธีการ
boolean find()
ค้นหาข้อความที่ป้อนสำหรับการจับคู่ครั้งต่อไป วิธีการนี้จะเริ่มการสแกนที่จุดเริ่มต้นของข้อความที่ระบุหรือที่อักขระตัวแรกหลังจากการจับคู่ครั้งก่อน ตัวเลือกที่สองจะเป็นไปได้ก็ต่อเมื่อการเรียกเมธอดนี้ก่อนหน้านี้คืนค่าเป็น "จริง" และไม่ได้รีเซ็ตรีโซลเวอร์ ไม่ว่าในกรณีใด หากการค้นหาสำเร็จ ค่าบูลีนที่เป็นจริงจะถูกส่งกลับ ตัวอย่างของวิธีนี้สามารถพบได้ในRegexDemo
ส่วนที่ 1 -
วิธีการนี้
boolean find(int start)
จะรีเซ็ตตัวจับคู่และค้นหาข้อความสำหรับการจับคู่ครั้งต่อไป การดูเริ่มต้นจากตำแหน่งที่ระบุโดยstart
พารามิเตอร์ หากการค้นหาสำเร็จ ค่าบูลีนจริงจะถูกส่งกลับ ตัวอย่างเช่นm.find(1);
สแกนข้อความโดยเริ่มจากตำแหน่ง1
(ตำแหน่ง 0 จะถูกละเว้น) หากพารามิเตอร์start
มีค่าลบหรือค่าที่มากกว่าความยาวของข้อความที่ตรงกัน วิธีการจะส่งข้อjava.lang.IndexOutOfBoundsException
ยกเว้น -
วิธีการนี้
boolean matches()
จะพยายามจับคู่ข้อความทั้งหมดกับรูปแบบ มันจะส่งกลับค่าบูลีนจริงหากข้อความทั้งหมดตรงกับรูปแบบ ตัวอย่างเช่น โค้ดPattern p = Pattern.compile("\\w*"); Matcher m = p.matcher("abc!"); System.out.println(p.matches());
แสดงผลfalse
เนื่องจากอักขระ!
ไม่ใช่อักขระคำ -
วิธีการนี้
boolean lookingAt()
จะพยายามจับคู่ข้อความที่กำหนดกับรูปแบบ เมธอดนี้จะคืนค่าเป็นจริงหากส่วนใดส่วนหนึ่งของข้อความตรงกับรูปแบบ ต่างจาก method นี้ตรงที่matches();
ข้อความทั้งหมดไม่จำเป็นต้องตรงกับรูปแบบ ตัวอย่างเช่นPattern p = Pattern.compile("\\w*"); Matcher m = p.matcher("abc!"); System.out.println(p.lookingAt());
จะส่งออกtrue
เนื่องจากจุดเริ่มต้นของข้อความabc!
ประกอบด้วยอักขระที่สร้างคำเท่านั้น
Pattern
คลาสอ็อบเจ็กต์ Matcher
บางครั้งคุณอาจต้องรีเซ็ตตัวจับคู่เพื่อล้างข้อมูลนี้หลังจากการค้นหารูปแบบเสร็จสิ้น วิธีการต่อไปนี้มีไว้เพื่อรีเซ็ตตัวแก้ไข:
-
วิธีการนี้
Matcher reset()
จะรีเซ็ตสถานะของตัวจับคู่ รวมถึงตำแหน่งที่จะต่อท้าย (รีเซ็ตเป็น 0) การดำเนินการค้นหารูปแบบถัดไปเริ่มต้นที่จุดเริ่มต้นของข้อความที่ตรงกัน การอ้างอิงถึงวัตถุปัจจุบันจะถูกส่งMatcher
กลับ ตัวอย่างเช่นm.reset();
รีเซ็ตตัวแก้ไขที่อ้างอิงm
โดย -
วิธีการ
Matcher reset(CharSequence text)
รีเซ็ตสถานะตัวแก้ไขและตั้งค่าข้อความตัวแก้ไขใหม่text
เป็น การดำเนินการค้นหารูปแบบถัดไปเริ่มต้นที่จุดเริ่มต้นของข้อความตัวจับคู่ใหม่ การอ้างอิงถึงวัตถุปัจจุบันจะถูกส่งMatcher
กลับ ตัวอย่างเช่นm.reset("new text");
รีเซ็ตรีโซลเวอร์ที่อ้างอิงm
และตั้งค่าข้อความรีโซลเวอร์ใหม่"new text"
เป็น

การเพิ่มข้อความต่อท้าย
java.lang.StringBuffer
ตำแหน่งของตัวจับคู่ที่จะต่อท้ายระบุจุดเริ่มต้นของข้อความตัวจับคู่ที่ต่อท้ายวัตถุประเภท วิธีการต่อไปนี้ใช้ตำแหน่งนี้:
-
วิธีการ อ่านอักขระ ข้อความ
Matcher appendReplacement(StringBuffer sb, String replacement)
ที่ตรงกันและต่อท้ายวัตถุStringBuffer
ที่อ้างอิงโดยอาร์กิวเมนต์sb
วิธีการนี้จะหยุดอ่านที่อักขระตัวสุดท้ายที่อยู่ก่อนหน้าการจับคู่รูปแบบก่อนหน้า ถัดไป วิธีการต่อท้ายอักขระจากออบเจ็กต์ประเภทString
ที่อ้างอิงโดยอาร์กิวเมนต์replacement
ต่อท้ายออบเจ็กต์StringBuffer
(สตริงreplacement
อาจมีการอ้างอิงถึงลำดับข้อความที่บันทึกไว้ระหว่างการค้นหาครั้งก่อน โดยระบุโดยใช้อักขระ($)
และหมายเลขกลุ่มที่ถูกจับ) สุดท้ายนี้ เมธอดจะตั้งค่าของตำแหน่งตัวจับคู่เพื่อต่อท้ายตำแหน่งของอักขระที่ตรงกันตัวสุดท้ายบวกหนึ่ง แล้วส่งคืนการอ้างอิงไปยังตัวจับคู่ปัจจุบัน -
วิธีการ
StringBuffer appendTail(StringBuffer sb)
เพิ่มข้อความทั้งหมดให้กับวัตถุStringBuffer
และส่งกลับการอ้างอิงไปยังวัตถุนั้น หลังจากการเรียกเมธอดครั้งล่าสุดappendReplacement(StringBuffer sb, String replacement)
ให้เรียกเมธอดappendTail(StringBuffer sb)
เพื่อคัดลอกข้อความที่เหลือไปยังออบเจ็กStringBuffer
ต์
วิธีการนี้Matcher appendReplacement(StringBuffer sb, String replacement)
จะส่งข้อยกเว้นjava.lang.IllegalStateException
หากผู้จับคู่ยังไม่พบรายการที่ตรงกันหรือความพยายามในการค้นหาครั้งก่อนล้มเหลว มันจะส่งข้อยกเว้นIndexOutOfBoundsException
หากบรรทัดreplacement
ระบุกลุ่มการจับที่ไม่อยู่ในรูปแบบ)
กลุ่มที่ถูกจับ |
---|
ดังที่คุณจำได้จากส่วนที่ 1 กลุ่มแคปเจอร์คือลำดับของอักขระที่อยู่ในวงเล็บ ( () ) เมตาอักขระ วัตถุประสงค์ของโครงสร้างนี้คือเพื่อจัดเก็บอักขระที่พบเพื่อนำมาใช้ใหม่ในภายหลังในระหว่างการจับคู่รูปแบบ ตัวละครทั้งหมดจากกลุ่มที่ถูกจับจะถือเป็นตัวละครตัวเดียวในระหว่างการค้นหารูปแบบ |
appendReplacement(StringBuffer sb, String replacement)
และappendTail(StringBuffer sb
เพื่อแทนที่ลำดับอักขระทั้งหมดในข้อความต้นฉบับcat
ด้วยcaterpillar
:
Pattern p = Pattern.compile("(cat)");
Matcher m = p.matcher("one cat, two cats, or three cats on a fence");
StringBuffer sb = new StringBuffer();
while (m.find())
m.appendReplacement(sb, "$1erpillar");
m.appendTail(sb);
System.out.println(sb);
การใช้กลุ่มที่บันทึกและการอ้างอิงถึงกลุ่มนั้นในข้อความแทนที่จะบอกให้โปรแกรมแทรกerpillar
หลังจากการเกิดขึ้นแต่ละครั้งของcat
. ผลลัพธ์ของการรันโค้ดนี้มีลักษณะดังนี้: one caterpillar, two caterpillars, or three caterpillars on a fence
การแทนที่ข้อความ
คลาสนี้Matcher
มีสองวิธีในการแทนที่ข้อความ ซึ่งเสริมกับappendReplacement(StringBuffer sb, String replacement)
. เมื่อใช้วิธีการเหล่านี้ คุณสามารถแทนที่รายการแรกของ [ข้อความที่ถูกแทนที่] หรือรายการทั้งหมดที่เกิดขึ้นได้:
-
วิธีการ
String replaceFirst(String replacement)
รีเซ็ตตัวจับคู่ สร้างวัตถุใหม่String
คัดลอกอักขระทั้งหมดของข้อความตัวจับคู่ (จนถึงคู่แรก) ไปยังสตริงนี้ เพิ่มอักขระต่อท้ายจากจุดสิ้นสุดreplacement
คัดลอกอักขระที่เหลือไปยังสตริง และส่งกลับ วัตถุString
(สตริงreplacement
สามารถมีการอ้างอิงถึงสิ่งที่บันทึกไว้ระหว่างลำดับข้อความค้นหาก่อนหน้าโดยใช้สัญลักษณ์ดอลลาร์และหมายเลขกลุ่มที่บันทึก) -
วิธี
String replaceAll(String replacement)
การทำงานคล้ายกับ methodString replaceFirst(String replacement)
แต่แทนที่replacement
รายการที่ตรงกันทั้งหมดด้วยอักขระจากสตริง
\s+
ค้นหาอักขระช่องว่างตั้งแต่หนึ่งตัวขึ้นไปในข้อความอินพุต ด้านล่างนี้เราจะใช้นิพจน์ทั่วไปนี้และเรียกวิธีreplaceAll(String replacement)
การลบช่องว่างที่ซ้ำกัน:
Pattern p = Pattern.compile("\\s+");
Matcher m = p.matcher("Удаляем \t\t лишние пробелы. ");
System.out.println(m.replaceAll(" "));
นี่คือผลลัพธ์: Удаляем лишние пробелы.
นิพจน์ทั่วไปใน Java ตอนที่ 4 นิพจน์ทั่วไปใน Java ส่วนที่ 5
GO TO FULL VERSION