Scanner vs. StringTokenizer vs. String.Split


155

Ich habe gerade die Scanner-Klasse von Java kennengelernt und frage mich jetzt, wie sie mit dem StringTokenizer und String.Split verglichen wird. Ich weiß, dass StringTokenizer und String.Split nur für Strings funktionieren. Warum sollte ich den Scanner für einen String verwenden? Ist der Scanner nur als One-Stop-Shopping für die Aufteilung gedacht?

Antworten:


240

Sie sind im Wesentlichen Pferde für Kurse.

  • Scannerwurde für Fälle entwickelt, in denen Sie eine Zeichenfolge analysieren und Daten verschiedener Typen abrufen müssen. Es ist sehr flexibel, bietet Ihnen aber wahrscheinlich nicht die einfachste API, um einfach ein Array von Zeichenfolgen abzurufen, die durch einen bestimmten Ausdruck begrenzt sind.
  • String.split()und Pattern.split()geben Sie eine einfache Syntax für Letzteres, aber das ist im Wesentlichen alles, was sie tun. Wenn Sie die resultierenden Zeichenfolgen analysieren oder das Trennzeichen je nach Token in der Mitte ändern möchten, helfen sie Ihnen dabei nicht weiter.
  • StringTokenizerist noch restriktiver als String.split()und auch etwas umständlicher zu bedienen. Es ist im Wesentlichen zum Herausziehen von Token vorgesehen, die durch feste Teilzeichenfolgen begrenzt sind. Aufgrund dieser Einschränkung ist es ungefähr doppelt so schnell wie String.split(). (Siehe meinen Vergleich von String.split()undStringTokenizer .) Es ist auch älter als die API für reguläre Ausdrücke, zu der auch String.split()ein Teil gehört.

Sie werden String.split()anhand meiner Timings feststellen, dass auf einem typischen Computer in wenigen Millisekunden immer noch Tausende von Zeichenfolgen als Token verwendet werden können . Darüber hinaus hat es den Vorteil, StringTokenizerdass Sie die Ausgabe als String-Array erhalten, was normalerweise gewünscht wird. Die Verwendung von Enumeration, wie von bereitgestellt StringTokenizer, ist die meiste Zeit zu "syntaktisch pingelig". Unter diesem Gesichtspunkt StringTokenizerist es heutzutage eine Verschwendung von Platz, und Sie können es auch einfach verwenden String.split().


8
Es wäre auch interessant, die Ergebnisse des Scanners bei denselben Tests zu sehen, die Sie mit String.Split und StringTokenizer ausgeführt haben.
Dave

2
Hat mir eine Antwort auf eine andere Frage gegeben: "Warum wird von der Verwendung von StringTokenizer abgeraten, wie in den Java-API-Hinweisen angegeben?". Aus diesem Text geht hervor, dass die Antwort "weil String.split () schnell genug ist" lautet.
Beine

1
Ist StringTokenizer jetzt ziemlich veraltet?
Steve the Maker

was soll man stattdessen verwenden? Scanner?
Adrian

4
Mir ist klar, dass es eine Antwort auf eine alte Frage ist, aber wenn ich einen riesigen Textstrom im laufenden Betrieb in Token aufteilen muss, ist das nicht StringTokenizerimmer noch meine beste Wahl, weil String.split()mir einfach der Speicher ausgeht?
Sergei Tachenov

57

Beginnen wir mit der Beseitigung StringTokenizer. Es wird alt und unterstützt nicht einmal reguläre Ausdrücke. In der Dokumentation heißt es:

StringTokenizerist eine Legacy-Klasse, die aus Kompatibilitätsgründen beibehalten wird, obwohl von ihrer Verwendung in neuem Code abgeraten wird. Es wird empfohlen, dass jeder, der diese Funktionalität sucht , stattdessen die splitMethode Stringoder das java.util.regexPaket verwendet.

Also werfen wir es sofort raus. Das lässt split()und Scanner. Was ist der Unterschied zwischen ihnen?

Zum einen wird split()einfach ein Array zurückgegeben, wodurch die Verwendung einer foreach-Schleife vereinfacht wird:

for (String token : input.split("\\s+") { ... }

Scanner ist eher wie ein Stream aufgebaut:

while (myScanner.hasNext()) {
    String token = myScanner.next();
    ...
}

oder

while (myScanner.hasNextDouble()) {
    double token = myScanner.nextDouble();
    ...
}

(Es hat eine ziemlich große API , denken Sie also nicht, dass es immer auf so einfache Dinge beschränkt ist.)

Diese Benutzeroberfläche im Stream-Stil kann nützlich sein, um einfache Textdateien oder Konsoleneingaben zu analysieren, wenn Sie nicht alle Eingaben haben (oder nicht erhalten können), bevor Sie mit dem Parsen beginnen.

Persönlich kann ich mich Scannernur an Schulprojekte erinnern , bei denen ich Benutzereingaben über die Befehlszeile erhalten musste. Es macht diese Art der Bedienung einfach. Aber wenn ich eine habe String, die ich aufteilen möchte, ist das fast ein Kinderspiel split().


20
StringTokenizer ist 2x so schnell wie String.split (). Wenn Sie keine regulären Ausdrücke verwenden MÜSSEN, NICHT!
Alex Worden

Ich habe gerade Scannerneue Zeilenzeichen in einer bestimmten erkannt String. Da neue Zeilenzeichen von Plattform zu Plattform variieren können (siehe PatternJavadoc!) Und die Eingabe der Zeichenfolge NICHT garantiert ist System.lineSeparator(), finde ich sie Scannerbesser geeignet, da sie bereits weiß, nach welchen neuen Zeilenzeichen beim Aufrufen zu suchen ist nextLine(). Denn String.splitich muss das richtige Regex-Muster eingeben, um Zeilentrennzeichen zu erkennen, die an keinem Standardspeicherort gespeichert sind (das Beste, was ich tun kann, ist, sie aus der ScannerQuelle der Klasse zu kopieren ).
ADTC

9

StringTokenizer war immer da. Es ist das schnellste von allen, aber die aufzählungsähnliche Redewendung sieht möglicherweise nicht so elegant aus wie die anderen.

Split entstand auf JDK 1.4. Langsamer als Tokenizer, aber einfacher zu verwenden, da es über die String-Klasse aufgerufen werden kann.

Der Scanner wurde auf JDK 1.5 ausgeführt. Es ist das flexibelste und füllt eine lange Lücke in der Java-API, um ein Äquivalent der berühmten Cs scanf-Funktionsfamilie zu unterstützen.


6

Wenn Sie ein String-Objekt haben, das Sie tokenisieren möchten, bevorzugen Sie die Verwendung der Split- Methode von String gegenüber einem StringTokenizer. Wenn Sie Textdaten aus einer Quelle außerhalb Ihres Programms analysieren, z. B. aus einer Datei oder vom Benutzer, ist ein Scanner hilfreich.


5
Einfach so, keine Rechtfertigung, kein Grund?
Januar

6

Split ist langsam, aber nicht so langsam wie Scanner. StringTokenizer ist schneller als Split. Ich stellte jedoch fest, dass ich durch den Handel mit etwas Flexibilität die doppelte Geschwindigkeit erreichen konnte, um einen Geschwindigkeitsschub zu erzielen, wie ich es bei JFastParser https://github.com/hughperkins/jfastparser getan habe

Testen an einer Zeichenfolge mit einer Million Doppel:

Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms

Ein bisschen Javadoc wäre nett gewesen, und was ist, wenn Sie etwas anderes als numerische Daten analysieren möchten?
NickJ

Nun, es ist auf Geschwindigkeit ausgelegt, nicht auf Schönheit. Es ist ganz einfach, nur ein paar Zeilen, also können Sie ein paar weitere Optionen für die Textanalyse hinzufügen, wenn Sie möchten.
Hugh Perkins

4

String.split scheint viel langsamer zu sein als StringTokenizer. Der einzige Vorteil bei Split ist, dass Sie eine Reihe von Token erhalten. Sie können auch beliebige reguläre Ausdrücke in Split verwenden. org.apache.commons.lang.StringUtils verfügt über eine Split-Methode, die viel schneller arbeitet als jede der beiden Methoden. StringTokenizer oder String.split. Die CPU-Auslastung ist jedoch für alle drei nahezu gleich. Wir brauchen also auch eine Methode, die weniger CPU-intensiv ist und die ich immer noch nicht finden kann.


3
Diese Antwort ist etwas unsinnig. Sie sagen, Sie suchen etwas, das schneller, aber "weniger CPU-intensiv" ist. Jedes Programm wird von der CPU ausgeführt. Wenn ein Programm Ihre CPU nicht zu 100% ausnutzt, muss es auf etwas anderes warten, z. B. E / A. Dies sollte bei der Erörterung der Zeichenfolgentokenisierung niemals ein Problem sein, es sei denn, Sie führen direkten Datenträgerzugriff durch (was wir hier insbesondere nicht tun).
Jolta

4

Ich habe kürzlich einige Experimente über die schlechte Leistung von String.split () in sehr leistungsempfindlichen Situationen durchgeführt. Sie können dies nützlich finden.

http://eblog.chrononsystems.com/hidden-evils-of-javas-stringsplit-and-stringr

Das Wesentliche ist, dass String.split () jedes Mal ein Muster für reguläre Ausdrücke kompiliert und somit Ihr Programm verlangsamen kann, verglichen mit der Verwendung eines vorkompilierten Musterobjekts und der direkten Verwendung für die Bearbeitung eines Strings.


4
Tatsächlich kompiliert String.split () das Muster nicht immer. Schauen Sie sich die Quelle an, wenn 1.7 Java, Sie werden sehen, dass überprüft wird, ob das Muster ein einzelnes Zeichen und kein Escapezeichen ist. Es teilt die Zeichenfolge ohne regulären Ausdruck, daher sollte es ziemlich schnell sein.
Krzysztof Krasoń

1

Für die Standardszenarien würde ich auch Pattern.split () vorschlagen, aber wenn Sie maximale Leistung benötigen (insbesondere unter Android sind alle von mir getesteten Lösungen ziemlich langsam) und Sie nur durch ein einziges Zeichen teilen müssen, verwende ich jetzt meine eigene Methode:

public static ArrayList<String> splitBySingleChar(final char[] s,
        final char splitChar) {
    final ArrayList<String> result = new ArrayList<String>();
    final int length = s.length;
    int offset = 0;
    int count = 0;
    for (int i = 0; i < length; i++) {
        if (s[i] == splitChar) {
            if (count > 0) {
                result.add(new String(s, offset, count));
            }
            offset = i + 1;
            count = 0;
        } else {
            count++;
        }
    }
    if (count > 0) {
        result.add(new String(s, offset, count));
    }
    return result;
}

Verwenden Sie "abc" .toCharArray (), um das char-Array für einen String abzurufen. Beispielsweise:

String s = "     a bb   ccc  dddd eeeee  ffffff    ggggggg ";
ArrayList<String> result = splitBySingleChar(s.toCharArray(), ' ');

1

Ein wichtiger Unterschied besteht darin, dass sowohl String.split () als auch Scanner leere Zeichenfolgen erzeugen können, StringTokenizer jedoch niemals.

Beispielsweise:

String str = "ab cd  ef";

StringTokenizer st = new StringTokenizer(str, " ");
for (int i = 0; st.hasMoreTokens(); i++) System.out.println("#" + i + ": " + st.nextToken());

String[] split = str.split(" ");
for (int i = 0; i < split.length; i++) System.out.println("#" + i + ": " + split[i]);

Scanner sc = new Scanner(str).useDelimiter(" ");
for (int i = 0; sc.hasNext(); i++) System.out.println("#" + i + ": " + sc.next());

Ausgabe:

//StringTokenizer
#0: ab
#1: cd
#2: ef
//String.split()
#0: ab
#1: cd
#2: 
#3: ef
//Scanner
#0: ab
#1: cd
#2: 
#3: ef

Dies liegt daran, dass das Trennzeichen für String.split () und Scanner.useDelimiter () nicht nur eine Zeichenfolge, sondern ein regulärer Ausdruck ist. Wir können das Trennzeichen "" im obigen Beispiel durch "+" ersetzen, damit sie sich wie StringTokenizer verhalten.


-5

String.split () funktioniert sehr gut, hat aber seine eigenen Grenzen. Wenn Sie beispielsweise einen String wie unten gezeigt anhand des Single- oder Double-Pipe-Symbols (|) teilen möchten, funktioniert dies nicht. In dieser Situation können Sie StringTokenizer verwenden.

ABC | IJK


12
Tatsächlich können Sie Ihr Beispiel mit nur "ABC | IJK" .split ("\\ |") teilen.
Tomo

"ABC || DEF ||" .split ("\\ |") funktioniert jedoch nicht wirklich, da die nachfolgenden zwei leeren Werte ignoriert werden, was das Parsen komplizierter macht, als es sein sollte.
Armand
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.