Replizieren String.split mit StringTokenizer
-
13-09-2019 - |
Frage
Ermutigt von dieser , und die Tatsache, ich Milliarden Zeichenfolge haben zu analysieren ich versuchte, meinen Code zu ändern, zu akzeptieren StringTokenizer statt String []
Das einzige, was zwischen mir gelassen und dem köstlichen x2 Leistungsschub bekommt, ist die Tatsache, dass, wenn Sie tun
"dog,,cat".split(",")
//output: ["dog","","cat"]
StringTokenizer("dog,,cat")
// nextToken() = "dog"
// nextToken() = "cat"
Wie kann ich ähnliche Ergebnisse mit den StringTokenizer erreichen? Gibt es schnellere Wege, dies zu tun?
Lösung
Sind Sie eigentlich nur Zeichenüber auf Kommas? Wenn ja, würde ich meine eigenen tokenizer schreiben - es auch noch effizienter als der allgemeineren Zweck kann am Ende StringTokenizer zu sein, die für mehrere Token aussehen können, und Sie können es jedoch machen verhalten Sie mögen. Für einen solchen einfachen Anwendungsfall kann es sich um eine einfache Implementierung sein.
Wenn es sinnvoll sei, könnten Sie sogar Iterable<String>
implementieren und erhalten verbesserte-for-Schleife Unterstützung mit starken Typisierung statt der Enumeration
Unterstützung durch StringTokenizer
zur Verfügung gestellt. Lassen Sie mich wissen, wenn Sie Hilfe wünschen Codierung ein solches Tier auf -. Es sollte wirklich nicht zu hart sein
Darüber hinaus würde ich versuchen, Performance-Tests auf Ihren aktuellen Daten läuft, bevor zu weit von einer bestehenden Lösung heraus springt. Sie haben keine Ahnung, wie viel von Ihrer Ausführungszeit ist wirklich in String.split
ausgegeben? Ich weiß, dass Sie eine Menge Saiten zu analysieren, aber wenn Sie etwas bedeutende danach mit ihnen zu tun sind, würde ich erwarten, dass als die Spaltung viel bedeutender sein.
Andere Tipps
Nach Bastelei mit der StringTokenizer
Klasse , kann ich nicht einen Weg finden, um die Anforderungen zu erfüllen ["dog", "", "cat"]
zurückzukehren.
Darüber hinaus wird die StringTokenizer
Klasse links nur aus Kompatibilitätsgründen, und die Verwendung von String.split
ist encouaged. Von der API-Spezifikation für die StringTokenizer
:
StringTokenizer
ist ein Vermächtnis Klasse das ist für die Kompatibilität beibehalten Gründe obwohl seine Verwendung ist in neuem Code abgeraten. Es ist empfohlen, dass jemand diese suchen Funktionellesplit
Methode vonString
oderjava.util.regex
Paket statt.
Da das Problem ist die angeblich schlechte Leistung des String.split
Methode, müssen wir eine Alternative finden.
Hinweis: Ich sage „angeblich schlechte Leistung“, weil es schwer ist, zu bestimmen, dass jeder Anwendungsfall in dem StringTokenizer
überlegen führen wird sein auf die String.split
Methode. Darüber hinaus in vielen Fällen, es sei denn, die tokenization der Saiten sind in der Tat der Engpass der Anwendung durch geeignete Profilierung bestimmt, ich fühle, dass es sich um eine vorzeitige Optimierung zu sein, wenn etwas am Ende wird. Ich würde geneigt sein, Code schreiben zu sagen, dass, bevor Sie sich auf die Optimierung zu verstehen, sinnvoll und einfach ist.
Nun, von den aktuellen Anforderungen, wahrscheinlich unsere eigenen tokenizer rollen wäre nicht allzu schwierig.
Rollen eigene tokenzier!
Das folgende ist ein einfaches tokenizer ich geschrieben habe. Ich sollte anmerken, dass es keine Geschwindigkeitsoptimierungen sind, noch gibt es Fehlerkontrollen vorbei am Ende der Schnur zu verhindern - das ist eine schnelle und unsaubere Implementierung:
class MyTokenizer implements Iterable<String>, Iterator<String> {
String delim = ",";
String s;
int curIndex = 0;
int nextIndex = 0;
boolean nextIsLastToken = false;
public MyTokenizer(String s, String delim) {
this.s = s;
this.delim = delim;
}
public Iterator<String> iterator() {
return this;
}
public boolean hasNext() {
nextIndex = s.indexOf(delim, curIndex);
if (nextIsLastToken)
return false;
if (nextIndex == -1)
nextIsLastToken = true;
return true;
}
public String next() {
if (nextIndex == -1)
nextIndex = s.length();
String token = s.substring(curIndex, nextIndex);
curIndex = nextIndex + 1;
return token;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
Die MyTokenizer
wird ein String
tokenize und String
als Trennzeichen, und verwenden Sie die String.indexOf
Methode die Suche nach Trennzeichen durchzuführen. Tokens werden von dem String.substring
Verfahren hergestellt wird.
Ich würde vermuten, könnte es einige Leistungsverbesserungen werden durch am char[]
Ebene auf die Saite arbeitet und nicht auf der String
Ebene. Aber das werde ich den Leser als Übung überlassen.
Die Klasse implementiert auch Iterable
und Iterator
, um die Vorteile des for-each
zu nehmen Schleife Konstrukt, das in Java 5. StringTokenizer
eingeführt wurde, ist ein Enumerator
, und unterstützt nicht die for-each
Konstrukt.
Ist es nicht schneller?
Um herauszufinden, ob dies ist schneller, schrieb ich ein Programm auf Geschwindigkeiten in den folgenden vier Methoden vergleichen:
- Die Verwendung von
StringTokenizer
. - Die Verwendung des neuen
MyTokenizer
. - Die Verwendung von
String.split
. - Die Verwendung von vorkompilierten regulären Ausdruck von
Pattern.compile
.
In den vier Methoden wurde die Zeichenfolge "dog,,cat"
in Token getrennt. Obwohl die StringTokenizer
im Vergleich enthalten ist, sei darauf hingewiesen, dass es nicht das gewünschte Ergebnis ["dog", "", "cat]
zurückkehren wird.
Die Tokenisieren wurden für insgesamt 1 Million Mal wiederholt genug Zeit zu geben, um die Unterschiede in den Methoden zu bemerken.
Der Code für die einfache Benchmark war die folgende:
long st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
StringTokenizer t = new StringTokenizer("dog,,cat", ",");
while (t.hasMoreTokens()) {
t.nextToken();
}
}
System.out.println(System.currentTimeMillis() - st);
st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
MyTokenizer mt = new MyTokenizer("dog,,cat", ",");
for (String t : mt) {
}
}
System.out.println(System.currentTimeMillis() - st);
st = System.currentTimeMillis();
for (int i = 0; i < 1e6; i++) {
String[] tokens = "dog,,cat".split(",");
for (String t : tokens) {
}
}
System.out.println(System.currentTimeMillis() - st);
st = System.currentTimeMillis();
Pattern p = Pattern.compile(",");
for (int i = 0; i < 1e6; i++) {
String[] tokens = p.split("dog,,cat");
for (String t : tokens) {
}
}
System.out.println(System.currentTimeMillis() - st);
Die Ergebnisse
Die Tests waren run Java SE 6 mit (build 1.6.0_12-b04), und die Ergebnisse waren wie folgt vor:
Run 1 Run 2 Run 3 Run 4 Run 5 ----- ----- ----- ----- ----- StringTokenizer 172 188 187 172 172 MyTokenizer 234 234 235 234 235 String.split 1172 1156 1171 1172 1156 Pattern.compile 906 891 891 907 906
So, wie aus den begrenzten Tests und nur fünf Läufen zu sehen ist, die StringTokenizer
in der Tat die schnellsten kommen, aber die MyTokenizer
kam als enges 2. in. Dann war String.split
die langsamste und der vorkompilierte reguläre Ausdruck war etwas schneller als die split
Methode.
Wie bei jedem kleinen Maßstab, ist es wahrscheinlich nicht sehr repräsentativ für Realbedingungen, so sollten die Ergebnisse mit einem Korn genommen werden (oder einem Hügel) Salz.
Hinweis: einige schnelle Benchmarks getan, Scanner stellt sich heraus, etwa viermal langsamer als String.split zu sein. Daher nicht Scanner verwenden.
(Ich verlasse die Post auf die Tatsache zu erfassen, dass Scanner eine schlechte Idee, in diesem Fall ist (lesen als:. Sie downvote mich nicht für Scanner was darauf hindeutet, bitte ...))
Angenommen, Sie Java 1.5 oder höher verwenden, versuchen Sie Scanner , die Iterator<String>
implementiert, wie es geschieht:
Scanner sc = new Scanner("dog,,cat");
sc.useDelimiter(",");
while (sc.hasNext()) {
System.out.println(sc.next());
}
gibt:
dog
cat
Je nachdem, welche Art von Zeichenketten Sie tokenize benötigen, können Sie Ihre eigenen Splitter auf String.indexOf () beispielsweise auf Basis schreiben. Sie könnten auch eine Multi-Core-Lösung, um die Leistung noch weiter zu verbessern, da die tokenization von Strings erstellen voneinander unabhängig ist. Die Arbeiten an Chargen von -Ermöglicht Say-100 Strings pro Kern. Haben die String.split () oder watever anderes.
Anstatt StringTokenizer, könnten Sie die StrTokenizer Klasse von Apache Commons Lang versuchen, die ich zitiere:
Diese Klasse kann ein String in viele kleinere Strings aufgeteilt. Ziel ist es, einen ähnlichen Job zu StringTokenizer zu tun, aber es ist viel mehr Kontrolle und Flexibilität bietet, einschließlich dem ListIterator Schnittstelle implementiert.
Leere Token können als null entfernt oder zurückgeschickt werden.
Das klingt wie das, was Sie brauchen, denke ich?
Sie könnte so etwas tun. Es ist nicht perfekt, aber es könnte für Sie arbeiten.
public static List<String> find(String test, char c) {
List<String> list = new Vector<String>();
start;
int i=0;
while (i<=test.length()) {
int start = i;
while (i<test.length() && test.charAt(i)!=c) {
i++;
}
list.add(test.substring(start, i));
i++;
}
return list;
}
Wenn möglich, können Sie die Liste, was ommit und direkt etwas tun, um die Teilkette:
public static void split(String test, char c) {
int i=0;
while (i<=test.length()) {
int start = i;
while (i<test.length() && test.charAt(i)!=c) {
i++;
}
String s = test.substring(start,i);
// do something with the string here
i++;
}
}
Auf meinem System die letzte Methode ist schneller als die StringTokenizer-Lösung, aber Sie könnten testen wollen, wie es für Sie arbeitet. (Natürlich könnte man diese Methode macht ein wenig kürzer ommiting die {} der zweiten, während Aussehen und natürlich könnten Sie ein verwenden for-Schleife anstelle der äußeren while-Schleife einschließlich dem letzten i ++ in dem, aber ich didn‘ t tun, dass hier, weil ich so schlecht Stil betrachten.
Nun, das schnellste, was man tun könnte, wäre, manuell die Zeichenfolge zu durchqueren, z.B.
List<String> split(String s) {
List<String> out= new ArrayList<String>();
int idx = 0;
int next = 0;
while ( (next = s.indexOf( ',', idx )) > -1 ) {
out.add( s.substring( idx, next ) );
idx = next + 1;
}
if ( idx < s.length() ) {
out.add( s.substring( idx ) );
}
return out;
}
Der (informelle Test) sieht aus, als Split etwas wie doppelt so schnell zu sein. Allerdings ist es ein bisschen gefährlich, so zu wiederholen, zum Beispiel auf entkam Komma wird es brechen, und wenn Sie am Ende brauchen mit, dass an einem gewissen Punkt zu behandeln (weil Ihre Liste von einer Milliarde Strings hat 3 entkam Komma) durch die Zeit, die Sie Hat es erlauben Sie werden wahrscheinlich einige des Geschwindigkeitsvorteils am Ende verlieren.
Schließlich ist es wahrscheinlich nicht wert, die stören.
Ich würde empfehlen, Google Guava Splitter
.
Ich verglich es mit coobird Test und bekam folgende Ergebnisse:
StringTokenizer 104
Google Guava Splitter 142
String.split 446
regexp 299
Wenn Sie Ihre Eingabe strukturiert ist, können Sie einen Blick auf die JavaCC Compiler. Er erzeugt eine Java-Klasse das Lesen Sie Ihre Eingabe. Es würde wie folgt aussehen:
TOKEN { <CAT: "cat"> , <DOG:"gog"> }
input: (cat() | dog())*
cat: <CAT>
{
animals.add(new Animal("Cat"));
}
dog: <DOG>
{
animals.add(new Animal("Dog"));
}