Сканер против StringTokenizer против String.Split - PullRequest
149 голосов
/ 27 марта 2009

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

Ответы [ 10 ]

234 голосов
/ 27 марта 2009

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

  • 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().

57 голосов
/ 27 марта 2009

Давайте начнем с исключения 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().

идти почти не сложно.
9 голосов
/ 27 марта 2009

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

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

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

6 голосов
/ 11 октября 2012

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

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

Scanner: 10642 ms
Split: 715 ms
StringTokenizer: 544ms
JFastParser: 290ms
5 голосов
/ 27 марта 2009

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

4 голосов
/ 26 января 2012

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

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

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

4 голосов
/ 05 ноября 2009

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

1 голос
/ 21 апреля 2016

Одно важное отличие состоит в том, что и 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.

1 голос
/ 12 марта 2015

Для сценариев по умолчанию я бы также предложил 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 (), чтобы получить массив символов для String. Например:

String s = "     a bb   ccc  dddd eeeee  ffffff    ggggggg ";
ArrayList<String> result = splitBySingleChar(s.toCharArray(), ' ');
0 голосов
/ 23 февраля 2013

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

ABC | IJK

...