После измельчения с классом StringTokenizer
я не смог найти способ удовлетворить требования по возврату ["dog", "", "cat"]
.
Кроме того, класс StringTokenizer
оставлен только по соображениям совместимости, а также использование String.split
. Из спецификации API для StringTokenizer
:
StringTokenizer
является устаревшим классом , который сохраняется для совместимости причин, хотя его использование не рекомендуется в новом коде. Это рекомендуется, чтобы любой, кто ищет эту функцию , использует метод split
of String
или java.util.regex
пакет вместо этого.
Поскольку проблема является предположительно низкой производительностью метода String.split
, нам нужно найти альтернативу.
Примечание: Я говорю «якобы плохую работу», потому что трудно определить, что каждый случай использования будет приводить в StringTokenizer
превосходя методу String.split
. Кроме того, во многих случаях, если токенизация строк действительно является узким местом приложения, определяемым надлежащим профилированием, я чувствую, что в конечном итоге это будет преждевременная оптимизация. Я был бы склонен сказать, что писать код, который имеет смысл и легко понять, прежде чем приступать к оптимизации.
Теперь, исходя из текущих требований, возможно, скользящий наш собственный токенизатор не будет слишком сложным.
Сверните свой собственный токензиер!
Следующий простой токенизатор, который я написал. Должно отметить, что нет скорости оптимизаций, ни там ошибки провер, чтобы предотвратить проходя мимо конца строки - это быстрая и грязная реализация:
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
примет String
tokenize и String
в качестве разделителя и использовать метод String.indexOf
для выполнения поиска разделителей. Токены производятся методом String.substring
.
Я бы предположил, что могут быть некоторые улучшения в производительности, работая над строкой на уровне char[]
, а не на уровне String
. Но я оставлю это упражнение для читателя.
Класс также реализует Iterable
и Iterator
для того, чтобы воспользоваться преимуществами конструкций for-each
петли, которая была введена в Java 5. StringTokenizer
является Enumerator
, и не поддерживает for-each
конструкцию.
Быстрее ли это?
Для того, чтобы выяснить, если это быстрее, я написал программу для сравнения скорости в следующих четырех способов:
- Использование
StringTokenizer
.
- Использование нового
MyTokenizer
.
- Использование
String.split
.
- Использование предварительно скомпилированного регулярного выражения на
Pattern.compile
.
В четырех методах строка "dog,,cat"
была разделена на жетоны. Хотя значение StringTokenizer
включено в сравнение, следует отметить, что он не вернет желаемый результат ["dog", "", "cat]
.
Повторяемость была повторена в общей сложности 1 миллион раз, чтобы дать достаточно времени, чтобы заметить разницу в методах.
Код, используемый для простого теста была следующей:
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);
Полученные результаты
Испытания проводились с использованием Java SE 6 (сборка 1.6.0_12-B04), и результаты были следующее:
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
Таким образом, как видно из ограниченного тестирования и только пять трасс, то StringTokenizer
сделал на самом деле с ome out the fastest, но MyTokenizer
пришел в себя как второй.Затем String.split
был самым медленным, а предварительно скомпилированное регулярное выражение было немного быстрее, чем метод split
.
Как и в случае с любым небольшим эталоном, он, по-видимому, не очень репрезентативен в реальных условиях, поэтому результаты должны приниматься с зерном (или насыпью) соли.
+1, мне это нравится идея реализации Iterable! –
coobird
Спасибо, Джон, я провел парсинг (используя множество индексов), и теперь он быстрее x4! – Dani