Поработав с классом 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
.
Как и в случае любого небольшого теста, он, вероятно, не очень репрезентативен для реальных условий, поэтому к результатам следует относиться с недоверием (или с горкой).
person
coobird
schedule
12.06.2009