Po majstrowaniu przy klasie StringTokenizer
, nie mogłem znaleźć sposobu na spełnienie wymagań dotyczących zwrotu ["dog", "", "cat"]
.
Ponadto, klasa StringTokenizer
jest pozostawiona tylko ze względu na kompatybilność, a użycie String.split
jest zabezpieczone. Od specyfikacji API dla StringTokenizer
:
StringTokenizer
jest klasa dziedzictwo że zostaje zachowana dla kompatybilności powodów chociaż jego stosowanie jest zniechęcony w nowym kodem. Zaleca się, aby każdy, kto szukał tej funkcji , używał zamiast tego pakietu split
lub .
Ponieważ problem jest rzekomo słabym wynikiem metody String.split
, musimy znaleźć alternatywę.
Uwaga: Mówię „podobno słabe działanie”, ponieważ trudno jest ustalić, że każdy przypadek użycia będzie skutkować StringTokenizer
są lepsze metody String.split
. Ponadto, w wielu przypadkach, chyba że tokenizacja strun jest rzeczywiście wąskim gardłem aplikacji określonej przez właściwe profilowanie, uważam, że w ostateczności będzie to przedwczesna optymalizacja, jeśli w ogóle. Byłbym skłonny powiedzieć napisać kod, który jest znaczący i łatwy do zrozumienia, przed wyruszeniem na optymalizację.
Teraz, z obecnych wymagań, prawdopodobnie toczenia własnego tokenizera nie byłoby zbyt trudne.
Roll nasz własny tokenzier!
Oto prosty tokenizer, który napisałem. Należy zauważyć, że nie istnieją żadne optymalizacje prędkości, ani nie ma błędów kontrole, aby zapobiec dzieje poza końcem napisu - to szybkie i-brudny realizacja:
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();
}
}
MyTokenizer
zajmie String
tokenize i String
jako ogranicznik, i użyj metody String.indexOf
, aby wykonać wyszukiwanie ograniczników. Tokeny są produkowane metodą String.substring
.
Podejrzewam, że można poprawić wydajność, pracując na łańcuchu na poziomie char[]
, a nie na poziomie . Ale zostawię to jako ćwiczenie dla czytelnika.
Klasa realizuje również Iterable
i Iterator
w celu skorzystania z for-each
pętli konstruktu, który został wprowadzony w Java 5. StringTokenizer
jest Enumerator
i nie obsługuje for-each
konstrukcję.
Czy to jest szybsze?
Aby dowiedzieć się, czy jest to szybciej, napisałem program do porównywania prędkości w czterech następujących metod:
- Zastosowanie
StringTokenizer
.
- Użycie nowego
MyTokenizer
.
- Korzystanie z
String.split
.
- Używanie prekompilowanego wyrażenia regularnego przez
Pattern.compile
.
W czterech metodach ciąg "dog,,cat"
został rozdzielony na tokeny. Chociaż StringTokenizer
jest uwzględnione w porównaniu, należy zauważyć, że nie zwróci on pożądanego wyniku z ["dog", "", "cat]
.
Tokenowanie powtórzono w sumie 1 milion razy, aby dać wystarczająco dużo czasu, aby zauważyć różnicę w metodach.
kod wykorzystywany do prostej odniesienia był następujący:
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);
Efekty
Testy przeprowadzano stosując Java SE 6 (build 1.6.0_12-B04), a wyniki następujące:
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
Więc, jak widać z ograniczonego badania i tylko pięciu seriach przesunięcie StringTokenizer
rzeczywiście c ome się najszybciej, ale MyTokenizer
zajął drugie miejsce.Następnie, String.split
był najwolniejszy, a prekompilowany wyrażenie regularne było nieco szybsze niż metoda split
.
Podobnie jak w przypadku każdego małego testu porównawczego, prawdopodobnie nie jest on bardzo reprezentatywny dla rzeczywistych warunków, więc wyniki należy pobrać za pomocą ziarna (lub kopca) soli.
+1, Lubię to pomysł wdrożenia Iterable! –
coobird
Dzięki Jon, ręcznie parsowałem (używając wielu indeksów) i teraz jest to x4 szybciej! – Dani