ביטויים רגולריים בג'אווה, חלק 1: התאמת תבניות ומחלקת התבניות

הדמות של Java ושיעורי המחרוזות המגוונים מציעים תמיכה ברמה נמוכה להתאמת תבניות, אך תמיכה זו מובילה בדרך כלל לקוד מורכב. לקידוד פשוט ויעיל יותר, Java מציעה את ממשק ה- API של Regex. הדרכה דו-חלקית זו מסייעת לך להתחיל בביטויים רגולריים וב- API של Regex. ראשית נפרוק את שלושת הכיתות החזקות השוכנות java.util.regexבחבילה, ואז נחקור את Patternהכיתה ואת מבני ההתאמה המתוחכמים שלה.

הורד קבל את הקוד הורד את קוד המקור למשל יישומים במדריך זה. נוצר על ידי ג'ף פרייזן עבור JavaWorld.

מהם ביטויים רגולריים?

ביטוי רגולרי , הידוע גם בתור regex או regexp , הוא מחרוזת אשר דפוס (תבנית) מתאר קבוצה של מחרוזות. התבנית קובעת אילו מיתרים שייכים לסט. דפוס מורכב מדמויות מילוליות וממטא - תווים , שהם תווים בעלי משמעות מיוחדת במקום משמעות מילולית.

התאמת תבניות היא תהליך חיפוש טקסט לזיהוי התאמות , או מחרוזות התואמות לדפוס של regex. Java תומכת בהתאמת תבניות באמצעות ה- Regex API שלה. ה- API מורכב משלושה classes-- Pattern, Matcher, ו PatternSyntaxException--all ממוקם java.util.regexהחבילה:

  • Patternאובייקטים, המכונים גם דפוסים , מורכבים מחדש.
  • Matcherאובייקטים, או התאמות , הם מנועים המפרשים דפוסים לאיתור התאמות ברצפי תווים (אובייקטים שהמחלקות שלהם מיישמים את java.lang.CharSequenceהממשק ומשמשים כמקורות טקסט).
  • PatternSyntaxException אובייקטים מתארים דפוסי regex לא חוקיים.

Java מספקת תמיכה גם בהתאמת תבניות בשיטות שונות java.lang.Stringבכיתתה. לדוגמה, boolean matches(String regex)מחזיר נכון רק אם מחרוזת ההפעלה תואמת במדויק ל- regexregex.

שיטות נוחות

מאחורי הקלעים, matches()ואת Stringשיטות נוחות regex מוכווני אחרות של" מיושמות במונחים של API Regex.

RegexDemo

אני כבר יצרתי את RegexDemoהיישום להפגין ביטויים רגולריים של Java ואת השיטות השונות מוקם Pattern, Matcher, ו PatternSyntaxExceptionכיתות. הנה קוד המקור להדגמה:

רישום 1. הדגמת regexes

import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; public class RegexDemo { public static void main(String[] args) { if (args.length != 2) { System.err.println("usage: java RegexDemo regex input"); return; } // Convert new-line (\n) character sequences to new-line characters. args[1] = args[1].replaceAll("\\\\n", "\n"); try { System.out.println("regex = " + args[0]); System.out.println("input = " + args[1]); Pattern p = Pattern.compile(args[0]); Matcher m = p.matcher(args[1]); while (m.find()) System.out.println("Found [" + m.group() + "] starting at " + m.start() + " and ending at " + (m.end() - 1)); } catch (PatternSyntaxException pse) { System.err.println("Bad regex: " + pse.getMessage()); System.err.println("Description: " + pse.getDescription()); System.err.println("Index: " + pse.getIndex()); System.err.println("Incorrect pattern: " + pse.getPattern()); } } }

הדבר הראשון RegexDemoשל" main()השיטה עושה זה כדי לאמת שורת הפקודה שלו. זה דורש שני ארגומנטים: הארגומנט הראשון הוא regex, והארגומנט השני הוא טקסט קלט כדי להתאים אותו ל regex.

כדאי לציין תו בשורה חדשה ( \n) כחלק מטקסט הקלט. הדרך היחידה להשיג זאת היא לציין \דמות ולאחריה nדמות. main()ממיר את רצף התווים הזה לערך Unicode 10.

עיקר RegexDemoהקוד של" ממוקם try- catchהמבנה. tryהבלוק הראשון כפלט את כל הטקסט regex קלט שצוין ולאחר מכן יוצר Patternאובייקט המאחסן רגולרי הידור. (רגקסים נערכים כדי לשפר את הביצועים במהלך התאמת תבניות.) התאמה נשלפת Patternמהאובייקט ומשמשת לחיפוש אחר התאמות שוב ושוב עד שלא נשאר. catchלחסום מפעילה שונות PatternSyntaxExceptionשיטות כדי לחלץ מידע שימושי על הכלל. מידע זה יוצג לאחר מכן.

אינך צריך לדעת יותר על פעולות קוד המקור בשלב זה; זה יתברר כשתחקור את ה- API בחלק 2. אתה צריך לאסוף רישום 1, עם זאת. תפוס את הקוד מרישום 1 והקלד את השורה הבאה בשורת הפקודה שלך כדי להרכיב RegexDemo:

javac RegexDemo.java

תבנית ובניה

Pattern, הראשון מבין שלוש המחלקות הכוללות את ממשק ה- API של Regex, הוא ייצוג מורכב של ביטוי רגולרי. Patternתיעוד ה- SDK מתאר מבני regex שונים, אך אלא אם כן אתה כבר משתמש regex מושבע, יתכן שאתה מתבלבל מחלקים בתיעוד. מה הם quantifiers ומה ההבדל בין חמדן , ששים , ו רכושני quantifiers? מהן כיתות אופי , matchers הגבול , אזכור בחזרה , ואת הבעות הדגל המשובץ ? אענה על שאלות אלו ועוד בסעיפים הבאים.

מיתרים מילוליים

מבנה regex הפשוט ביותר הוא המחרוזת המילולית. חלק מהטקסט הקלט חייב להתאים לתבנית הבנייה הזו כדי לקבל התאמה מוצלחת לתבנית. שקול את הדוגמה הבאה:

java RegexDemo apple applet

דוגמה זו מנסה לגלות אם יש התאמה appleלתבנית appletבטקסט הקלט. התפוקה הבאה חושפת את ההתאמה:

regex = apple input = applet Found [apple] starting at 0 and ending at 4

הפלט מראה לנו את ה- regex והטקסט הקלט, ואז מציין התאמה מוצלחת של appleפנים applet. בנוסף, הוא מציג את מדדי ההתחלה והסיום של המשחק כי: 0ו 4, בהתאמה. אינדקס ההתחלה מזהה את מיקום הטקסט הראשון בו מתרחשת התאמה לתבנית; אינדקס הסיום מזהה את מיקום הטקסט האחרון להתאמה.

עכשיו נניח שאנחנו מציינים את שורת הפקודה הבאה:

java RegexDemo apple crabapple

הפעם, אנו מקבלים את ההתאמה הבאה עם אינדקסי התחלה וסיום שונים:

regex = apple input = crabapple Found [apple] starting at 4 and ending at 8

התרחיש ההפוך, בו appletהוא ה- regex והוא appleטקסט הקלט, אינו מגלה התאמה. כל ה- regex חייב להתאים, ובמקרה זה טקסט הקלט אינו מכיל tafter apple.

מטא-תווים

מבני regex חזקים יותר משלבים דמויות מילוליות עם מטא-תווים. לדוגמה, ב- a.b, המטא- .תו התקופתי ( ) מייצג כל תו שמופיע בין aלבין b. שקול את הדוגמה הבאה:

java RegexDemo .ox "The quick brown fox jumps over the lazy ox."

דוגמה זו מציינת .oxכ- regex וכטקסט The quick brown fox jumps over the lazy ox.הקלט. RegexDemoמחפש בטקסט התאמות שמתחילות בכל תו ומסתיימות בתו ox. זה מייצר את התפוקה הבאה:

regex = .ox input = The quick brown fox jumps over the lazy ox. Found [fox] starting at 16 and ending at 18 Found [ ox] starting at 39 and ending at 41

הפלט מגלה שני התאמות: foxו ox(עם אופי החלל המוביל). .Metacharacter תואם fבמשחק הראשון ואת אופי החלל במשחק השני.

מה קורה כשאנחנו מחליפים .oxאת המטא-אופי התקופתי? כלומר, מה התפוקה שנובעת מציון שורת הפקודה הבאה:

java RegexDemo . "The quick brown fox jumps over the lazy ox."

מכיוון שהמטא-אופי של התקופה תואם לכל תו, RegexDemoמוציא התאמה לכל תו (כולל תו התקופה המסיימת) בטקסט הקלט:

regex = . input = The quick brown fox jumps over the lazy ox. Found [T] starting at 0 and ending at 0 Found [h] starting at 1 and ending at 1 Found [e] starting at 2 and ending at 2 Found [ ] starting at 3 and ending at 3 Found [q] starting at 4 and ending at 4 Found [u] starting at 5 and ending at 5 Found [i] starting at 6 and ending at 6 Found [c] starting at 7 and ending at 7 Found [k] starting at 8 and ending at 8 Found [ ] starting at 9 and ending at 9 Found [b] starting at 10 and ending at 10 Found [r] starting at 11 and ending at 11 Found [o] starting at 12 and ending at 12 Found [w] starting at 13 and ending at 13 Found [n] starting at 14 and ending at 14 Found [ ] starting at 15 and ending at 15 Found [f] starting at 16 and ending at 16 Found [o] starting at 17 and ending at 17 Found [x] starting at 18 and ending at 18 Found [ ] starting at 19 and ending at 19 Found [j] starting at 20 and ending at 20 Found [u] starting at 21 and ending at 21 Found [m] starting at 22 and ending at 22 Found [p] starting at 23 and ending at 23 Found [s] starting at 24 and ending at 24 Found [ ] starting at 25 and ending at 25 Found [o] starting at 26 and ending at 26 Found [v] starting at 27 and ending at 27 Found [e] starting at 28 and ending at 28 Found [r] starting at 29 and ending at 29 Found [ ] starting at 30 and ending at 30 Found [t] starting at 31 and ending at 31 Found [h] starting at 32 and ending at 32 Found [e] starting at 33 and ending at 33 Found [ ] starting at 34 and ending at 34 Found [l] starting at 35 and ending at 35 Found [a] starting at 36 and ending at 36 Found [z] starting at 37 and ending at 37 Found [y] starting at 38 and ending at 38 Found [ ] starting at 39 and ending at 39 Found [o] starting at 40 and ending at 40 Found [x] starting at 41 and ending at 41 Found [.] starting at 42 and ending at 42

ציטוט מטא-תווים

כדי לציין .או כל מטא-אופי כדמות מילולית במבנה regex, ציטט את המטא-אופי באחת מהדרכים הבאות:

  • הקדימו את המטא-אופי עם אופי קו נטוי.
  • Place the metacharacter between \Q and \E (e.g., \Q.\E).

Remember to double each backslash character (as in \\. or \\Q.\\E) that appears in a string literal such as String regex = "\\.";. Don't double the backslash character when it appears as part of a command-line argument.

Character classes

We sometimes need to limit characters that will produce matches to a specific character set. For example, we might search text for vowels a, e, i, o, and u, where any occurrence of a vowel indicates a match. A character class identifies a set of characters between square-bracket metacharacters ([ ]), helping us accomplish this task. Pattern supports simple, negation, range, union, intersection, and subtraction character classes. We'll look at all of these below.

Simple character class

The simple character class consists of characters placed side by side and matches only those characters. For example, [abc] matches characters a, b, and c.

Consider the following example:

java RegexDemo [csw] cave

This example matches only c with its counterpart in cave, as shown in the following output:

regex = [csw] input = cave Found [c] starting at 0 and ending at 0

Negation character class

The negation character class begins with the ^ metacharacter and matches only those characters not located in that class. For example, [^abc] matches all characters except a, b, and c.

Consider this example:

java RegexDemo "[^csw]" cave

Note that the double quotes are necessary on my Windows platform, whose shell treats the ^ character as an escape character.

This example matches a, v, and e with their counterparts in cave, as shown here:

regex = [^csw] input = cave Found [a] starting at 1 and ending at 1 Found [v] starting at 2 and ending at 2 Found [e] starting at 3 and ending at 3

Range character class

The range character class consists of two characters separated by a hyphen metacharacter (-). All characters beginning with the character on the left of the hyphen and ending with the character on the right of the hyphen belong to the range. For example, [a-z] matches all lowercase alphabetic characters. It's equivalent to specifying [abcdefghijklmnopqrstuvwxyz].

Consider the following example:

java RegexDemo [a-c] clown

This example matches only c with its counterpart in clown, as shown:

regex = [a-c] input = clown Found [c] starting at 0 and ending at 0

Merging multiple ranges

You can merge multiple ranges into the same range character class by placing them side by side. For example, [a-zA-Z] matches all lowercase and uppercase alphabetic characters.

Union character class

The union character class consists of multiple nested character classes and matches all characters that belong to the resulting union. For example, [a-d[m-p]] matches characters a through d and m through p.

Consider the following example:

java RegexDemo [ab[c-e]] abcdef

This example matches a, b, c, d, and e with their counterparts in abcdef:

regex = [ab[c-e]] input = abcdef Found [a] starting at 0 and ending at 0 Found [b] starting at 1 and ending at 1 Found [c] starting at 2 and ending at 2 Found [d] starting at 3 and ending at 3 Found [e] starting at 4 and ending at 4

Intersection character class

The intersection character class consists of characters common to all nested classes and matches only common characters. For example, [a-z&&[d-f]] matches characters d, e, and f.

Consider the following example:

java RegexDemo "[aeiouy&&[y]]" party

שים לב שהציטוטים הכפולים נחוצים בפלטפורמת Windows שלי, שהקליפה שלה מתייחסת &לדמות כמפריד פקודות.

דוגמה זו תואמת רק yאת מקבילתה ב party:

regex = [aeiouy&&[y]] input = party Found [y] starting at 4 and ending at 4