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?

Dave
quelle

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().

Neil Coffey
quelle
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().

Michael Myers
quelle
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.

H Marcelo Morales
quelle
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.

Bill die Eidechse
quelle
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
Hugh Perkins
quelle
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.

Manish
quelle
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.

pdeva
quelle
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(), ' ');
Simon
quelle
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.

John29
quelle
-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

Mujahid Shaik
quelle
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