Java StringTokenizer
Strings in Java mit dem veralteten StringTokenizer aufteilen und wann stattdessen String.split verwendet werden sollte.
java.util.StringTokenizer ist die ursprüngliche JDK-Klasse zum „Aufteilen eines Strings in Teile" — ein zeichenweiser Tokenizer, der seit Version 1.0 zur Plattform gehört. Das Javadoc selbst rät davon für neuen Code ab: „StringTokenizer ist eine Legacy-Klasse, die aus Kompatibilitätsgründen beibehalten wird, obwohl ihre Verwendung in neuem Code nicht empfohlen wird. Es wird empfohlen, dass alle, die diese Funktionalität suchen, stattdessen die Methode split von String oder das Paket java.util.regex verwenden."
Das ist die Kernaussage, und sie ist korrekt — StringTokenizer taucht jedoch weiterhin in echten Codebasen auf und hat einige legitime Nischen. Dieses Kapitel behandelt ihn kurz und zeigt klar, wann stattdessen String.split oder Scanner zu verwenden sind.
Die grundlegende Schleife
Ein StringTokenizer ist eine Enumeration von Teilstrings:
StringTokenizer st = new StringTokenizer("apple banana cherry");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
// apple
// banana
// cherryDie Standardmenge der Trennzeichen ist Leerzeichen — Leerzeichen, Tabulator, Zeilenumbruch, Wagenrücklauf, Seitenvorschub. Aufeinanderfolgende Trennzeichen werden zusammengefasst: " a b " ergibt genau zwei Token.
Benutzerdefinierte Trennzeichen
Der zweite Konstruktor nimmt einen String, dessen Zeichen jeweils als Trennzeichen behandelt werden — nicht als Trennzeichenfolge:
StringTokenizer st = new StringTokenizer("one,two;three|four", ",;|");
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
// one
// two
// three
// fourDas ist die Besonderheit im Design, die viele Menschen verwirrt. new StringTokenizer(input, ", ") behandelt , und Leerzeichen jeweils als Trennzeichen, nicht die zweizeichige Folge ", ". Wenn Sie mehrteilige Trennzeichen benötigen, ist StringTokenizer nicht mehr ausreichend.
Die Trennzeichen selbst zurückgeben
Der dreiargumentige Konstruktor steuert, ob Trennzeichen selbst als Token ausgegeben werden:
StringTokenizer st = new StringTokenizer("a+b*c", "+*", true);
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
// a
// +
// b
// *
// cDas ist das eine Feature, das String.split nicht direkt nachbildet: Dafür würde man einen Matcher und einen regulären Ausdruck benötigen. Für sehr einfaches Ausdrucks-Parsing — die Art von Aufgabe, die keinen Lexer rechtfertigt — ist diese Überladung noch nützlich.
Token im Voraus zählen
countTokens() gibt an, wie viele Token noch verbleiben (d.h. durch wiederholte nextToken()-Aufrufe erzeugt würden). Es verbraucht sie nicht.
StringTokenizer st = new StringTokenizer("a b c d");
int n = st.countTokens(); // 4
while (st.hasMoreTokens()) st.nextToken();
n = st.countTokens(); // 0Das ist gelegentlich nützlich, wenn ein Ausgabe-Array der richtigen Größe allokiert werden soll — obwohl man mit String.split einfach .split(...).length aufrufen würde.
Das Trennzeichen zur Laufzeit ändern
Ein weniger bekanntes Feature: nextToken(String newDelims) setzt die Trennzeichenmenge für diesen Aufruf und alle folgenden neu. Nach der Änderung gilt die neue Menge für nachfolgende hasMoreTokens()/nextToken()-Aufrufe, bis Sie sie erneut ändern.
StringTokenizer st = new StringTokenizer("key1=value1; key2=value2; key3=value3");
while (st.hasMoreTokens()) {
String pair = st.nextToken("; ").trim(); // tokens separated by ';' or space
System.out.println(pair);
}Für ad-hoc-Einmal-Parsing kann das praktisch sein. Für wartbaren Code ist es verwirrend — Leser erwarten nicht, dass sich eine Trennzeichenmenge innerhalb einer Schleife ändert.
Warum String.split in der Regel besser ist
Die Gründe, warum String.split (oder Pattern.compile(...).split) für neuen Code bevorzugt werden sollte:
- Echte Regex-Trennzeichen. Mehrteilige Trennzeichen, Zeichenklassen, Alternation — alles natürlich möglich.
StringTokenizerverarbeitet nur einzelne Zeichen als Trennzeichen. - Leere Token sind sichtbar.
"a,,b".split(",")gibt["a", "", "b"]zurück.StringTokenizerüberspringt das leere Token stillschweigend. Bei CSV-ähnlicher Eingabe ist „das zweite Feld war leer" eine Information, die man in der Regel benötigt. - Gibt ein Array zurück. Einfach zu indizieren, einfach in eine
Listumzuwandeln, einfach zu streamen. - Im Allgemeinen schneller unter JIT dank
Pattern-Caching für einfache Split-Muster. - Einfacher zu testen, einfacher zu lesen. Eine Tokenizer-Schleife liest sich wie 1996;
parts = csv.split(",")beschreibt die Absicht klar.
Wann man ihn trotzdem verwenden könnte
Eine kurze Liste von Fällen, in denen StringTokenizer noch vertretbar ist:
- Streaming über einen sehr langen String, bei dem Token für Token verarbeitet werden soll, ohne ein Array aller Token im Speicher zu halten.
StringTokenizerallokiert das Ergebnis nicht im Voraus;splitschon. - Trennzeichen als Token zurückgeben für den einfachstmöglichen Tokenizer, ohne
Pattern/Matcherhinzuzuziehen. - Alten Code warten, in dem der Rest der Datei ihn verwendet und Konsistenz das Freundlichste für den nächsten Leser ist.
Für alles andere: Verwenden Sie split.
Ein ausgearbeitetes Beispiel
Das folgende Programm tokenisiert drei Eingaben auf drei verschiedene Arten, jeweils nebeneinander mit den äquivalenten split-Aufrufen, damit die Verhaltensunterschiede sichtbar sind:
Zwei Erkenntnisse aus der Ausgabe. In Fall 1 gibt split das leere Feld zwischen green und blue aus ([red, green, , blue]); der Tokenizer fasst es zusammen ([red, green, blue]). In Fall 2 erzeugen beide zufällig dieselben Token, aber aus unterschiedlichen Gründen: Der Tokenizer teilt mix an jedem ; und jedem Leerzeichen unabhängig voneinander auf ("; " bedeutet „jedes dieser Zeichen ist ein Trennzeichen"), während split("; ") die wörtliche zweizeichige Folge "; " abgleicht. Sie stimmen hier nur überein, weil die Trennzeichen in mix genau ; sind. Ändert man die Eingabe auf "k1=v1 ;k2=v2", weichen beide sofort voneinander ab. Welches Verhalten auch immer gewünscht wird — der Code sollte es explizit machen, und das ist mit split viel einfacher.
Was kommt als Nächstes
Bisher haben wir Strings in Teile aufgeteilt. Oft ist es das eigentliche Ziel, einen String in eine Zahl, einen boolean oder ein anderes Primitiv umzuwandeln — und umgekehrt, Primitive zurück in Strings. Dieser Hin- und Rückweg hat seine eigenen Hilfsmethoden und Fallstricke. Weiter geht es mit Java-String-Konvertierungen.