CodeGym /Java Blog /Willekeurig /Reguliere expressies in Java
John Squirrels
Niveau 41
San Francisco

Reguliere expressies in Java

Gepubliceerd in de groep Willekeurig
Reguliere expressies is een onderwerp dat programmeurs, zelfs ervaren programmeurs, vaak uitstellen tot later. Maar vroeg of laat krijgen de meeste Java-ontwikkelaars te maken met tekstuele informatie. Meestal betekent dit het zoeken en bewerken van tekst. Zonder reguliere expressies is effectieve en compacte tekstverwerkingscode simpelweg ondenkbaar. Dus stop met uitstellen, laten we nu reguliere expressies aanpakken. Het is niet zo moeilijk. Reguliere expressies in Java - 1

Wat is een reguliere expressie (regex)?

In feite is een reguliere expressie een patroon voor het vinden van een tekenreeks in tekst. In Java is de oorspronkelijke representatie van dit patroon altijd een string, dwz een object van de Stringklasse. Het is echter geen tekenreeks die in een reguliere expressie kan worden gecompileerd - alleen tekenreeksen die voldoen aan de regels voor het maken van reguliere expressies. De syntaxis wordt gedefinieerd in de taalspecificatie. Reguliere expressies worden geschreven met behulp van letters en cijfers, evenals metatekens, dit zijn tekens die een speciale betekenis hebben in de syntaxis van reguliere expressies. Bijvoorbeeld:

String regex = "java"; // The pattern is "java";
String regex = "\\d{3}"; // The pattern is three digits;

Reguliere expressies maken in Java

Het maken van een reguliere expressie in Java omvat twee eenvoudige stappen:
  1. schrijf het als een tekenreeks die voldoet aan de syntaxis van reguliere expressies;
  2. compileer de string in een reguliere expressie;
In elk Java-programma beginnen we met reguliere expressies te werken door een Patternobject te maken. Om dit te doen, moeten we een van de twee statische methoden van de klasse aanroepen: compile. De eerste methode neemt één argument - een letterlijke tekenreeks die de reguliere expressie bevat, terwijl de tweede een extra argument gebruikt dat de instellingen voor patroonovereenkomst bepaalt:

public static Pattern compile (String literal)
public static Pattern compile (String literal, int flags)
De lijst met mogelijke waarden van de flagsparameter wordt in klasse gedefinieerd Patternen is voor ons beschikbaar als statische klassevariabelen. Bijvoorbeeld:

Pattern pattern = Pattern.compile("java", Pattern.CASE_INSENSITIVE); // Pattern-matching will be case insensitive.
Kortom, de Patternklasse is een constructor voor reguliere expressies. Onder de motorkap compileroept de methode de Patternprivéconstructor van de klasse aan om een ​​gecompileerde weergave te maken. Dit mechanisme voor het maken van objecten wordt op deze manier geïmplementeerd om onveranderlijke objecten te creëren. Wanneer een reguliere expressie wordt gemaakt, wordt de syntaxis ervan gecontroleerd. Als de string fouten bevat, PatternSyntaxExceptionwordt er een gegenereerd.

Syntaxis van reguliere expressies

De syntaxis van reguliere expressies is afhankelijk van de <([{\^-=$!|]})?*+.>tekens, die kunnen worden gecombineerd met letters. Afhankelijk van hun rol kunnen ze worden onderverdeeld in verschillende groepen:
1. Metatekens voor het matchen van de grenzen van lijnen of tekst
Metakarakter Beschrijving
^ begin van een regel
$ einde van een regel
\B woord grens
\B niet-woordgrens
\A begin van de invoer
\G einde van de vorige wedstrijd
\Z einde van de invoer
\z einde van de invoer
2. Metatekens voor het matchen van vooraf gedefinieerde tekenklassen
Metakarakter Beschrijving
\D cijfer
\D niet-cijferig
\S witruimte karakter
\S niet-witruimte karakter
\w alfanumeriek teken of onderstrepingsteken
\W elk teken behalve letters, cijfers en onderstrepingstekens
. elk karakter
3. Metatekens voor overeenkomende controletekens
Metakarakter Beschrijving
\T tab karakter
\N nieuwe regel karakter
\R koets terug
\F linefeed karakter
\u0085 volgende regel karakter
\u2028 lijn scheidingsteken
\u2029 scheidingsteken voor alinea's
4. Metatekens voor tekenklassen
Metakarakter Beschrijving
[abc] een van de vermelde tekens (a, b of c)
[^abc] elk ander teken dan de vermelde (geen a, b of c)
[a-zA-Z] samengevoegde bereiken (Latijnse tekens van a tot z, niet hoofdlettergevoelig)
[advertentie[mp]] unie van karakters (van a tot d en van m tot p)
[az&&[def]] snijpunt van karakters (d, e, f)
[az&&[^bc]] aftrekken van karakters (a, dz)
5. Metatekens voor het aangeven van het aantal karakters (quantifiers). Een kwantor wordt altijd voorafgegaan door een teken of tekengroep.
Metakarakter Beschrijving
? een of geen
* nul of meer keer
+ een of meerdere keren
{N} n keer
{N,} n of meer keer
{n,m} minstens n keer en niet meer dan m keer

Hebzuchtige kwantoren

Een ding dat u over kwantoren moet weten, is dat ze in drie verschillende varianten voorkomen: hebzuchtig, bezitterig en onwillig. U maakt een kwantor bezittelijk door een " +"-teken achter de kwantor toe te voegen. Je maakt het terughoudend door " ?" toe te voegen. Bijvoorbeeld:

"A.+a" // greedy
"A.++a" // possessive
"A.+?a" // reluctant
Laten we proberen dit patroon te gebruiken om te begrijpen hoe de verschillende soorten kwantoren werken. Standaard zijn kwantoren hebzuchtig. Dit betekent dat ze zoeken naar de langste match in de string. Als we de volgende code uitvoeren:

public static void main(String[] args) {
    String text = "Fred Anna Alexander";
    Pattern pattern = Pattern.compile("A.+a");
    Matcher matcher = pattern.matcher(text);
    while (matcher.find()) {
        System.out.println(text.substring(matcher.start(), matcher.end()));
    }
}
we krijgen deze uitvoer:

Anna Alexa
Voor de reguliere expressie " A.+a" wordt patroonvergelijking als volgt uitgevoerd:
  1. Het eerste teken in het opgegeven patroon is de Latijnse letter A. Matchervergelijkt het met elk teken van de tekst, beginnend bij index nul. Het karakter Fstaat op index nul in onze tekst, dus Matcheritereert door de karakters totdat het overeenkomt met het patroon. In ons voorbeeld staat dit karakter op index 5.

    Reguliere expressies in Java - 2
  2. Zodra er een overeenkomst is gevonden met het eerste teken van het patroon, Matcherwordt gezocht naar een overeenkomst met het tweede teken. In ons geval is dit het .teken " ", dat staat voor elk teken.

    Reguliere expressies in Java - 3

    Het personage nstaat op de zesde positie. Het komt zeker in aanmerking als een match voor "elk personage".

  3. Matchergaat verder met het controleren van het volgende teken van het patroon. In ons patroon is het opgenomen in de kwantor die van toepassing is op het voorgaande teken: " .+". Omdat het aantal herhalingen van "elk teken" in ons patroon één of meerdere keren is, wordt Matcherherhaaldelijk het volgende teken uit de tekenreeks genomen en vergeleken met het patroon zolang het overeenkomt met "elk teken". In ons voorbeeld — tot het einde van de tekenreeks (van index 7 tot index 18).

    Reguliere expressies in Java - 4

    Kortom, Matcherslokt de snaar op tot het einde - dit is precies wat wordt bedoeld met "hebzuchtig".

  4. Nadat Matcher het einde van de tekst heeft bereikt en de controle voor het " A.+"-gedeelte van het patroon heeft voltooid, begint het met het controleren van de rest van het patroon: a. Er is geen tekst meer in de toekomst, dus de controle gaat verder door "achteruit te gaan", beginnend bij het laatste teken:

    Reguliere expressies in Java - 5
  5. Matcher"onthoudt" het aantal herhalingen in het " .+" deel van het patroon. Op dit punt wordt het aantal herhalingen met één verminderd en wordt het grotere patroon vergeleken met de tekst totdat er een overeenkomst is gevonden:

    Reguliere expressies in Java - 6

Bezittelijke kwantoren

Bezittelijke kwantoren lijken veel op hebzuchtige. Het verschil is dat wanneer tekst is vastgelegd tot het einde van de tekenreeks, er geen patroonovereenkomst is tijdens het "terugtrekken". Met andere woorden, de eerste drie fasen zijn hetzelfde als voor hebzuchtige kwantoren. Na het vastleggen van de hele string, voegt de matcher de rest van het patroon toe aan wat hij overweegt en vergelijkt het met de vastgelegde string. In ons voorbeeld, met de reguliere expressie " A.++a", vindt de main-methode geen overeenkomst. Reguliere expressies in Java - 7

Onwillige kwantoren

  1. Voor deze kwantoren zoekt de code, net als bij de hebzuchtige variant, naar een overeenkomst op basis van het eerste teken van het patroon:

    Reguliere expressies in Java - 8
  2. Vervolgens zoekt het naar een overeenkomst met het volgende teken van het patroon (elk teken):

    Reguliere expressies in Java - 9
  3. In tegenstelling tot hebzuchtige patroonvergelijking, wordt bij onwillige patroonvergelijking naar de kortste overeenkomst gezocht. Dit betekent dat na het vinden van een overeenkomst met het tweede teken van het patroon (een punt, dat overeenkomt met het teken op positie 6 in de tekst, wordt Matchergecontroleerd of de tekst overeenkomt met de rest van het patroon — het teken " a"

    Reguliere expressies in Java - 10
  4. De tekst komt niet overeen met het patroon (dwz het bevat het teken " n" bij index 7), dus Matchervoegt er meer een "elk teken" aan toe, omdat de kwantor er een of meer aangeeft. Vervolgens vergelijkt het het patroon opnieuw met de tekst op de posities 5 tot en met 8:

    Reguliere expressies in Java - 11
  5. In ons geval is er een match gevonden, maar zijn we nog niet aan het einde van de tekst. Daarom begint het matchen van patronen opnieuw vanaf positie 9, dwz dat er naar het eerste teken van het patroon wordt gezocht met behulp van een vergelijkbaar algoritme en dit wordt herhaald tot het einde van de tekst.

    Reguliere expressies in Java - 12
Dienovereenkomstig mainkrijgt de methode het volgende resultaat bij gebruik van het patroon " A.+?a": Anna Alexa Zoals je in ons voorbeeld kunt zien, produceren verschillende soorten kwantoren verschillende resultaten voor hetzelfde patroon. Houd hier dus rekening mee en kies de juiste variëteit op basis van wat u zoekt.

Escape-tekens in reguliere expressies

Omdat een reguliere expressie in Java, of beter gezegd de oorspronkelijke representatie ervan, een letterlijke tekenreeks is, moeten we rekening houden met de Java-regels met betrekking tot letterlijke tekenreeksen. Met name het backslash-teken " \" in letterlijke tekenreeksen in de Java-broncode wordt geïnterpreteerd als een besturingsteken dat de compiler vertelt dat het volgende teken speciaal is en op een speciale manier moet worden geïnterpreteerd. Bijvoorbeeld:

String s = "The root directory is \nWindows"; // Move "Windows" to a new line
String s = "The root directory is \u00A7Windows"; // Insert a paragraph symbol before "Windows"
Dit betekent dat letterlijke tekenreeksen die reguliere expressies beschrijven en " \"-tekens gebruiken (dwz om metatekens aan te geven) de backslashes moeten herhalen om ervoor te zorgen dat de Java bytecode-compiler de tekenreeks niet verkeerd interpreteert. Bijvoorbeeld:

String regex = "\\s"; // Pattern for matching a whitespace character
String regex = "\"Windows\"";  // Pattern for matching "Windows"
Dubbele backslashes moeten ook worden gebruikt om speciale tekens te ontwijken die we als "normale" tekens willen gebruiken. Bijvoorbeeld:

String regex = "How\\?";  // Pattern for matching "How?"

Methoden van de klasse Pattern

De Patternklasse heeft andere methoden om met reguliere expressies te werken:
  • String pattern()‒ geeft de oorspronkelijke tekenreeksrepresentatie van de reguliere expressie die is gebruikt om het Patternobject te maken:

    
    Pattern pattern = Pattern.compile("abc");
    System.out.println(pattern.pattern()); // "abc"
    
  • static boolean matches(String regex, CharSequence input)– hiermee kunt u de reguliere expressie controleren die is doorgegeven als regex tegen de tekst die is doorgegeven als input. Geeft terug:

    waar – als de tekst overeenkomt met het patroon;
    onwaar - als dat niet het geval is;

    Bijvoorbeeld:

    
    System.out.println(Pattern.matches("A.+a","Anna")); // true
    System.out.println(Pattern.matches("A.+a","Fred Anna Alexander")); // false
    
  • int flags()flags‒ geeft de waarde terug van de parameterset van het patroon toen het patroon werd gemaakt of 0 als de parameter niet was ingesteld. Bijvoorbeeld:

    
    Pattern pattern = Pattern.compile("abc");
    System.out.println(pattern.flags()); // 0
    Pattern pattern = Pattern.compile("abc",Pattern.CASE_INSENSITIVE);
    System.out.println(pattern.flags()); // 2
    
  • String[] split(CharSequence text, int limit)– splitst de doorgegeven tekst in een Stringarray. De limitparameter geeft het maximale aantal overeenkomsten aan waarnaar in de tekst wordt gezocht:

    • als limit > 0limit-1overeenkomt;
    • if limit < 0‒ alle komt overeen met de tekst
    • if limit = 0‒ alle overeenkomsten in de tekst, lege strings aan het einde van de array worden weggegooid;

    Bijvoorbeeld:

    
    public static void main(String[] args) {
        String text = "Fred Anna Alexa";
        Pattern pattern = Pattern.compile("\\s");
        String[] strings = pattern.split(text,2);
        for (String s : strings) {
            System.out.println(s);
        }
        System.out.println("---------");
        String[] strings1 = pattern.split(text);
        for (String s : strings1) {
            System.out.println(s);
        }
    }
    

    Console-uitvoer:

    
    Fred
    Anna Alexa
    ---------
    Fred
    Anna
    Alexa
    

    Hieronder gaan we in op een andere methode van de klasse die wordt gebruikt om een Matcher​​object te maken.

Methoden van de Matcher-klasse

Instanties van de Matcherklasse worden gemaakt om patroonovereenkomsten uit te voeren. Matcheris de "zoekmachine" voor reguliere expressies. Om een ​​zoekopdracht uit te voeren, moeten we het twee dingen geven: een patroon en een startindex. Om een Matcher​​object te maken, Patternbiedt de klasse de volgende methode: рublic Matcher matcher(CharSequence input) De methode heeft een tekenreeks nodig, die zal worden doorzocht. Dit is een instantie van een klasse die de CharSequenceinterface implementeert. Je kunt niet alleen een String, maar ook een StringBuffer, StringBuilder, Segment, of doorgeven CharBuffer. Het patroon is een Patternobject waarop de matchermethode wordt aangeroepen. Voorbeeld van het maken van een matcher:

Pattern p = Pattern.compile("a*b"); // Create a compiled representation of the regular expression
Matcher m = p.matcher("aaaaab"); // Create a "search engine" to search the text "aaaaab" for the pattern "a*b"
Nu kunnen we onze "zoekmachine" gebruiken om naar overeenkomsten te zoeken, de positie van een overeenkomst in de tekst op te halen en tekst te vervangen met behulp van de methoden van de klasse. De boolean find()methode zoekt naar de volgende overeenkomst in de tekst. We kunnen deze methode en een loop-opdracht gebruiken om een ​​hele tekst te analyseren als onderdeel van een gebeurtenismodel. Met andere woorden, we kunnen noodzakelijke bewerkingen uitvoeren wanneer een gebeurtenis plaatsvindt, dwz wanneer we een overeenkomst in de tekst vinden. We kunnen bijvoorbeeld de klassen int start()en int end()methoden van deze klasse gebruiken om de positie van een overeenkomst in de tekst te bepalen. String replaceFirst(String replacement)En we kunnen de methoden en gebruiken String replaceAll(String replacement)om overeenkomsten te vervangen door de waarde van de vervangingsparameter. Bijvoorbeeld:

public static void main(String[] args) {
    String text = "Fred Anna Alexa";
    Pattern pattern = Pattern.compile("A.+?a");

    Matcher matcher = pattern.matcher(text);
    while (matcher.find()) {
        int start=matcher.start();
        int end=matcher.end();
        System.out.println("Match found: " + text.substring(start, end) + " from index "+ start + " through " + (end-1));
    }
    System.out.println(matcher.replaceFirst("Ira"));
    System.out.println(matcher.replaceAll("Mary"));
    System.out.println(text);
}
Uitgang:

Match found: Anna from index 5 through 8
Match found: Alexa from index 10 through 14
Fred Ira Alexa
Fred Mary Mary
Fred Anna Alexa
Het voorbeeld maakt duidelijk dat de methoden replaceFirsten replaceAlleen nieuw Stringobject maken — een tekenreeks waarin patroonovereenkomsten in de oorspronkelijke tekst worden vervangen door de tekst die als argument aan de methode is doorgegeven. Bovendien replaceFirstvervangt de methode alleen de eerste overeenkomst, maar replaceAllvervangt de methode alle overeenkomsten in de tekst. De oorspronkelijke tekst blijft ongewijzigd. De meest voorkomende regex-bewerkingen van de klassen Patternen Matcherzijn rechtstreeks in de Stringklasse ingebouwd. Dit zijn methoden zoals split, matches, replaceFirst, en replaceAll. Maar onder de motorkap gebruiken deze methoden de Patternen Matcherklassen. Dus als je tekst wilt vervangen of strings in een programma wilt vergelijken zonder extra code te schrijven, gebruik dan de methoden van hetStringklas. Als je meer geavanceerde functies nodig hebt, onthoud dan de klassen Patternen .Matcher

Conclusie

In een Java-programma wordt een reguliere expressie gedefinieerd door een tekenreeks die voldoet aan specifieke regels voor het matchen van patronen. Bij het uitvoeren van code compileert de Java-machine deze string tot een Patternobject en gebruikt een Matcherobject om overeenkomsten in de tekst te vinden. Zoals ik in het begin al zei, stellen mensen reguliere uitdrukkingen vaak uit tot later, omdat ze ze als een moeilijk onderwerp beschouwen. Maar als u de basissyntaxis, metatekens en karakter-escaping begrijpt en voorbeelden van reguliere expressies bestudeert, zult u merken dat ze veel eenvoudiger zijn dan ze op het eerste gezicht lijken.

Meer lezen:

Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION