После возни с классом StringTokenizer
я не смог найти способ удовлетворить требования для возврата ["dog", "", "cat"]
.
Кроме того, класс StringTokenizer
оставлен только из соображений совместимости, и использование String.split
рекомендуется. Из спецификации API для StringTokenizer
:
StringTokenizer
это унаследованный класс
это сохраняется для совместимости
причины, хотя его использование
обескуражен в новом коде. это
рекомендовал всем, кто ищет это
функциональность использовать метод split
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
для токенизации и 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
на самом деле оказался самым быстрым, но MyTokenizer
занял второе место. Тогда String.split
был самым медленным, а скомпилированное регулярное выражение было немного быстрее, чем метод split
.
Как и в любом небольшом тесте, он, вероятно, не очень характерен для реальных условий, поэтому результаты должны быть получены с зерном (или насыпью) соли.