Wie lassen sich Teilstrings in Strings finden?

Um einen String innerhalb eines Strings zu finden, können in Java mehrere Verfahren Verwendung finden. In diesem Artikel werden die wesentlichen, auf regulären Ausdrücken basierenden Vorgehensweisen besprochen.

Die Klasse java.util.regex.Matcher stellt eine Reihe von Methoden bereit, um das Vorkommen von Teilstrings in einem String zu überprüfen und deren jeweilige Position zu ermitteln. Durch die Nutzung eines regulären Ausdrucks kann dabei nach ganzen, einem Muster entsprechenden Gruppen gesucht werden. Dies stellt einen Vorteil gegenüber anderen Suchweisen dar.

Das Klassengerüst

Der Übersichtlichkeit halber werden die einzelnen Suchverfahren im Folgenden in Methoden gekapselt, die im Konstruktor einer Beispielklasse aufgerufen werden können. Der zu durchsuchende Text ist für einige Beispiel dabei in einer Klassenvariablen TXT deklariert, sodass sich hierfür das folgende Klassengerüst ergibt:

import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StringFinden {

    private final static String TXT = "Eine Rose ist eine Rose und weder eine famose Hose noch eine lose Dose.";

    public StringFinden() {
        // Methodenaufruf
    }

    // Methodendeklaration

    public static void main(String[] args) {
        new StringFinden();
    }
}

Suche mittels regulärem Ausdruck

Um die Suche durch einen regulären Ausdruck vorzubereiten, muss zunächst ein Pattern-Objekt in Form des vorkompilierten Ausdrucks erstellt werden. Dies wird durch die Übergabe seiner Stringrepräsentation an die statische Methode compile() der Klasse Pattern erreicht. Auf diesem Objekt wird dann ein Matcher durch Aufruf von matcher() erzeugt. Der Methode wird dabei der zu durchsuchende Textstring als Parameter übergeben.
Das Matcher-Objekt stellt dann die diversen Methoden zur Suche bereit.

Prüfung des vollständigen Strings

private void findAll() {
    Pattern p = Pattern.compile("\\b.ose.*");
    Matcher m = null;
    for (int i = 0; i < TXT.length(); i++) {
        m = p.matcher(TXT.substring(i));
        if (m.matches()) {
            System.out.println(m.group());
        }
    }
}

Die Methode Matcher#matches() überprüft den vollständigen String auf Übereinstimmung mit dem regulären Ausdruck. Sie kann hierdurch z.B. Verwendung finden, um innerhalb einer Schleife mehrere Textbereiche nacheinander zu überprüfen.
Das Beispiel oben durchläuft den Textstring zeichenweise und vergleicht das jeweilige Fragment von der Indexposition bis zum Ende mit dem regulären Ausdruck. Die Ausgabe ist wie folgt:

Rose ist eine Rose und weder eine famose Hose noch eine lose Dose.
Rose und weder eine famose Hose noch eine lose Dose.
mose Hose noch eine lose Dose.
Hose noch eine lose Dose.
lose Dose.
Dose.

Die Tatsache, dass wirklich der vollständige String dem Ausdruck entsprechen muss, lässt sich leicht überprüfen, indem man den regulären Ausdruck von \\b.ose.* zu \\b.ose abändert. Es findet dann keine Ausgabe statt, da nach der Silbe 'ose' in jedem Fall mindestens noch ein Zeichen folgt.
Zur Ausgabe des gefundenen Musters, wird hier Matcher#group() herangezogen.

Prüfung des String-Anfangs

private void findAllFromBeginning() {
    Pattern p = Pattern.compile("\\b.ose");
    Matcher m = p.matcher(TXT);
    for (int i = 0; i < TXT.length(); i++) {
        m = p.matcher(TXT.substring(i));
        if (m.lookingAt()) {
            System.out.println(m.group());
        }
    }
}

Die Methode Matcher#lookingAt() findet wie Matcher.matches() ebenfalls Einträge, die am Beginn des Textstrings stehen, berücksichtigt jedoch ggfs. nicht das Ende des Textstrings. Obige Methode findAllFromBeginning() erzeugt die folgende Ausgabe:

Rose
Rose
mose
Hose
lose
Dose

Erstes Vorkommen finden

private void findPart() {
    Pattern p = Pattern.compile("(\\b.ose)");
    Matcher m = p.matcher(TXT);
    if (m.find()) {
        System.out.println(m.group() + " found at index " + m.start() + " ending at " + m.end());
    }
}

Die Methode Matcher#find() findet das erste Vorkommen des Patterns innerhalb eines Strings und liefert erfolgsabhängig einen boolschen Wert. Die Anfangsposition des ermittelten Teilstrings kann durch Matcher#start() ermittelt werden. Die erste Position nach dem erkannten String lässt sich durch Matcher#end() finden. Die Methode liefert die folgende Ausgabe:

Rose found at index 5 ending at 9

Alle Vorkommen finden

private void findMultiPart() {
    Pattern p = Pattern.compile("(\\b.ose)");
    Matcher m = p.matcher(TXT);
    while (m.find()) {
        System.out.println(m.group() + " found at index " + m.start());
    }
}

Um alle Vorkommen eines Patterns in einem Textstring zu finden, kann Matcher#find() als Abbruchbedingung in einer While-Schleife genutzt werden. Der String wird durchlaufen und die Schleife terminiert, wenn kein Pattern mehr gefunden wird und find() somit false zurückgibt.
Die Ausgabe von findMultiPart():

Rose found at index 5
Rose found at index 19
Hose found at index 46
lose found at index 61
Dose found at index 66

Suche im Stream

private void findStream() {
    Pattern p = Pattern.compile("(\\b.ose)");
    Matcher m = p.matcher(TXT);
    m.results().map(MatchResult::group).forEach(s -> {
        System.out.println(s + " found at index " + m.start());
    });
}

Ab Java 9 kann die Patternsuche auch per Stream erfolgen. Hierzu liefert Matcher#results() einen Stream<MatchResult> in der Reihenfolge des Pattern-Vorkommens. MatchResult#group() gibt dann die jeweils gefundene Sequenz zurück. Die Ausgabe der obigen Methode findStream() entspricht derjenigen von findMultiPart().

Suchen - Ersetzen

Die Klasse Matcher stellt eine Reihe von Methoden bereit, die es ermöglichen, eine Suchen-Ersetzen-Funktion zu implementieren. Sollen alle dem Pattern entsprechenden Stringfragmente ersetzt werden, kann hierzu Matcher#replaceAll() verwendet werden. Der Methode wird hierzu der Ersatzstring als Parameter übergeben:

private void replaceAll() {
    Pattern p = Pattern.compile("(\\b.ose)");
    Matcher m = p.matcher("Eine Rose ist eine Rose und kein Krokus");
    String replaced = m.replaceAll("Tulpe");
    System.out.println(replaced);
}

Die Ausgabe zeigt:

Eine Tulpe ist eine Tulpe und kein Krokus

Soll nur das erste Vorkommen im String ersetzt werden, kann statt replaceAll() replaceFirst() Verwendung finden.

Eine weitere Möglichkeit ergibt sich, wenn das Ergebnis in einem StringBuffer-Objekt gespeichert werden soll, etwa bei der Bearbeitung sehr langer Texte. Hierzu wird Matcher#appendReplacement() genutzt. Der Methode werden zwei Parameter übergeben. Der erste, ein StringBuffer speichert das Ergebnis abschnittsweise nach erfolgtem Ersetzen, der zweite ist der String, der zum Austausch dient.

private void findReplace() {
    Pattern p = Pattern.compile("(\\b.ose)");
    Matcher m = p.matcher("Eine Rose ist eine Rose und kein Krokus");
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
        m.appendReplacement(sb, "Tulpe");
    }
    System.out.println(sb.toString());
    m.appendTail(sb);
    System.out.println(sb.toString());
}

Das Verfahren läuft hierbei folgendermaßen ab: Durch Matcher#find() wird über den Textstring iteriert. Wird das Pattern erkannt - und nur dann - werden der Stringbereich von Beginn an bis zum letzen Character vor der erkannten Character-Sequenz mit danach angefügtem Ersatzstring in den StringBuffer kopiert. Wird in der Folge ein weiteres Vorkommen gefunden, so wird auf die gleiche Weise nach dem Ende des vorhergehenden Fundes fortgefahren. Ein abschließender Teil des Textes, der nicht dem gesuchten Pattern entspricht, wird auf diese Weise jedoch nicht in den StringBuffer kopiert. Dies geschieht nach Abschluss der Suche durch Matcher#appendTail(). Die Ausgabe zeigt:

Eine Tulpe ist eine Tulpe
Eine Tulpe ist eine Tulpe und kein Krokus

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.