Сканер против StringTokenizer против String.Split

155

Я только что узнал о классе Java Scanner, и теперь мне интересно, как он сравнивается / конкурирует с StringTokenizer и String.Split. Я знаю, что StringTokenizer и String.Split работают только со строками, так зачем мне использовать сканер для строки? Сканер предназначен только для того, чтобы делать покупки в одном месте?

Дейв
источник

Ответы:

240

По сути, они лошади для курсов.

  • ScannerПредназначено для случаев, когда нужно разобрать строку, извлекая данные разных типов. Он очень гибкий, но, возможно, не дает вам простейшего API для простого получения массива строк, ограниченных определенным выражением.
  • String.split()и Pattern.split()дать вам простой синтаксис для выполнения последнего, но это по сути все, что они делают. Если вы хотите проанализировать результирующие строки или изменить разделитель на полпути в зависимости от конкретного токена, они вам не помогут.
  • StringTokenizerэто еще более ограничительно, чем использование String.split(), и немного более неудобно. Он по сути предназначен для извлечения токенов, ограниченных фиксированными подстроками. Из-за этого ограничения это примерно в два раза быстрее String.split(). (См. Мое сравнение String.split()иStringTokenizer .) Это также предшествует API регулярных выражений, частью которого String.split()является.

По моим String.split()временам вы заметите, что на обычной машине можно по-прежнему токенизировать тысячи строк за несколько миллисекунд . Кроме того, он имеет преимущество перед StringTokenizerтем, что дает вывод в виде строкового массива, который обычно является тем, что вы хотите. Использование Enumeration, как предусмотрено StringTokenizer, в большинстве случаев слишком «синтаксически суетливо». С этой точки зрения StringTokenizerв наше время это пустая трата пространства, и вы можете просто использовать String.split().

Нил Коффи
источник
8
Также было бы интересно посмотреть результаты Scanner на тех же тестах, которые вы выполняли на String.Split и StringTokenizer.
Дейв
2
Дали мне ответ на другой вопрос: «почему не рекомендуется использовать StringTokenizer, как указано в примечаниях по Java API?». Из этого текста кажется, что ответ будет «потому что String.split () достаточно быстрый».
Ноги
1
Итак, StringTokenizer в значительной степени устарел сейчас?
Стив Мейкер
что использовать вместо этого? Сканер?
Адриан
4
Я понимаю, что это ответ на старый вопрос, но если мне нужно разбить огромный поток текста на токены на лету, разве это не StringTokenizerлучший выбор, потому String.split()что просто не хватит памяти?
Сергей Таченов
57

Давайте начнем с устранения StringTokenizer. Он стареет и даже не поддерживает регулярные выражения. В его документации говорится:

StringTokenizerявляется устаревшим классом, который сохраняется по соображениям совместимости, хотя его использование не рекомендуется в новом коде. Всем, кто ищет эту функцию, рекомендуется использовать splitметод Stringили java.util.regexпакет.

Итак, давайте выбросим это прямо сейчас. Это оставляет split()и Scanner. Какая разница между ними?

Во-первых, split()просто возвращает массив, что упрощает использование цикла foreach:

for (String token : input.split("\\s+") { ... }

Scanner построен больше как поток:

while (myScanner.hasNext()) {
    String token = myScanner.next();
    ...
}

или

while (myScanner.hasNextDouble()) {
    double token = myScanner.nextDouble();
    ...
}

(У него довольно большой API , поэтому не думайте, что он всегда ограничен такими простыми вещами.)

Этот интерфейс в стиле потока может быть полезен для анализа простых текстовых файлов или ввода с консоли, когда у вас нет (или вы не можете получить) всех вводимых данных перед началом анализа.

Лично я могу вспомнить только один раз, когда использовал Scannerшкольные проекты, когда мне приходилось получать пользовательский ввод из командной строки. Это делает такую ​​операцию легкой. Но если у меня есть то, Stringчто я хочу разделить, это почти легкое дело split().

Майкл Майерс
источник
20
StringTokenizer в 2 раза быстрее String.split (). Если вам НЕ НУЖНО использовать регулярные выражения, НЕ делайте!
Алекс Уорден
Я просто использовал Scannerдля обнаружения новых символов строки в заданном String. Поскольку символы новой строки могут варьироваться от платформы (посмотрите на PatternJavadoc «s!) И строку ввода не гарантируют соответствие System.lineSeparator(), я считаю Scannerболее подходящим , как он уже знает , что символы новой строки искать при вызове nextLine(). Потому что String.splitмне придется ввести правильный шаблон регулярных выражений, чтобы обнаружить разделители строк, которые я не нахожу хранящимися ни в одном стандартном месте (лучшее, что я могу сделать, это скопировать его из Scannerисточника класса).
ADTC
9

StringTokenizer всегда был там. Это самый быстрый из всех, но идиома, похожая на перечисление, может выглядеть не так элегантно, как другие.

Сплит появился на JDK 1.4. Медленнее, чем токенизатор, но проще в использовании, так как он вызывается из класса String.

Сканер пришел на JDK 1.5. Он является наиболее гибким и заполняет давнишний пробел в Java API для поддержки эквивалента известного семейства функций Cs scanf.

H Марсело Моралес
источник
6

Если у вас есть объект String, который вы хотите токенизировать, используйте метод разделения String вместо StringTokenizer . Если вы анализируете текстовые данные из источника вне вашей программы, например из файла или от пользователя, то здесь вам пригодится сканер.

Билл Ящерица
источник
5
Просто так, без оправдания, без причины?
Jan.supol
6

Сплит медленный, но не такой медленный, как сканер. StringTokenizer быстрее, чем сплит. Однако я обнаружил, что могу получить двойную скорость, торгуя некоторой гибкостью, чтобы получить повышение скорости, что я и сделал на JFastParser https://github.com/hughperkins/jfastparser

Тестирование на строке, содержащей миллион дублей:

Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms
Хью Перкинс
источник
Некоторый Javadoc был бы хорош, и что, если вы хотите проанализировать что-то кроме числовых данных?
NickJ
Ну, это для скорости, а не для красоты. Это довольно просто, всего несколько строк, так что вы можете добавить еще несколько параметров для разбора текста, если хотите.
Хью Перкинс
4

String.split, кажется, намного медленнее, чем StringTokenizer. Единственным преимуществом split является то, что вы получаете массив токенов. Также вы можете использовать любые регулярные выражения в split. org.apache.commons.lang.StringUtils имеет метод split, который работает намного быстрее, чем любой из двух, а именно. StringTokenizer или String.split. Но загрузка ЦП для всех трех почти одинакова. Поэтому нам также нужен метод, который требует меньше ресурсов процессора, но я до сих пор не могу его найти.

Manish
источник
3
Этот ответ немного бессмысленный. Вы говорите, что ищете что-то более быстрое, но "менее интенсивное использование процессора". Любая программа выполняется процессором. Если программа не использует ваш процессор на 100%, то она должна ожидать чего-то другого, например ввода / вывода. Это не должно быть проблемой при обсуждении токенизации строк, если только вы не делаете прямой доступ к диску (чего мы, в частности, здесь не делаем).
Jolta
4

Недавно я провел несколько экспериментов по поводу плохой производительности String.split () в ситуациях с высокой производительностью. Вы можете найти это полезным.

http://eblog.chrononsystems.com/hidden-evils-of-javas-stringsplit-and-stringr

Суть в том, что String.split () каждый раз компилирует шаблон регулярного выражения и, таким образом, может замедлить вашу программу по сравнению с тем, если вы используете предварительно скомпилированный объект Pattern и используете его напрямую для работы со строкой.

pdeva
источник
4
На самом деле String.split () не всегда компилирует шаблон. Посмотрите на источник, если 1.7 java, вы увидите, что есть проверка, является ли шаблон одним символом, а не экранированным, он разделит строку без регулярного выражения, поэтому это должно быть довольно быстро.
Кшиштоф Красонь
1

Для сценариев по умолчанию я бы также предложил Pattern.split (), но если вам нужна максимальная производительность (особенно на Android, все протестированные мной решения работают довольно медленно) и вам нужно разделить только на один символ, я теперь использую свой собственный метод:

public static ArrayList<String> splitBySingleChar(final char[] s,
        final char splitChar) {
    final ArrayList<String> result = new ArrayList<String>();
    final int length = s.length;
    int offset = 0;
    int count = 0;
    for (int i = 0; i < length; i++) {
        if (s[i] == splitChar) {
            if (count > 0) {
                result.add(new String(s, offset, count));
            }
            offset = i + 1;
            count = 0;
        } else {
            count++;
        }
    }
    if (count > 0) {
        result.add(new String(s, offset, count));
    }
    return result;
}

Используйте «abc» .toCharArray (), чтобы получить массив char для String. Например:

String s = "     a bb   ccc  dddd eeeee  ffffff    ggggggg ";
ArrayList<String> result = splitBySingleChar(s.toCharArray(), ' ');
Саймон
источник
1

Одно важное отличие состоит в том, что и String.split (), и Scanner могут создавать пустые строки, но StringTokenizer никогда этого не делает.

Например:

String str = "ab cd  ef";

StringTokenizer st = new StringTokenizer(str, " ");
for (int i = 0; st.hasMoreTokens(); i++) System.out.println("#" + i + ": " + st.nextToken());

String[] split = str.split(" ");
for (int i = 0; i < split.length; i++) System.out.println("#" + i + ": " + split[i]);

Scanner sc = new Scanner(str).useDelimiter(" ");
for (int i = 0; sc.hasNext(); i++) System.out.println("#" + i + ": " + sc.next());

Вывод:

//StringTokenizer
#0: ab
#1: cd
#2: ef
//String.split()
#0: ab
#1: cd
#2: 
#3: ef
//Scanner
#0: ab
#1: cd
#2: 
#3: ef

Это связано с тем, что разделитель для String.split () и Scanner.useDelimiter () является не просто строкой, а регулярным выражением. Мы можем заменить разделитель "" на "+" в приведенном выше примере, чтобы заставить их вести себя как StringTokenizer.

John29
источник
-5

String.split () работает очень хорошо, но имеет свои границы, например, если вы хотите разбить строку, как показано ниже на основе символа одинарной или двойной трубы (|), она не работает. В этой ситуации вы можете использовать StringTokenizer.

ABC | IJK

Моджахед шайк
источник
12
На самом деле, вы можете разделить ваш пример просто с помощью "ABC | IJK" .split ("\\ |");
Томо
«ABC || DEF ||» .split («\\ |») на самом деле не работает, потому что он игнорирует завершающие два пустых значения, что делает анализ более сложным, чем должно быть.
Арманд