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


155

Щойно я дізнався про клас сканування Java, і тепер мені цікаво, як він порівнює / конкурує зі StringTokenizer та String.Split. Я знаю, що StringTokenizer і String.Split працюють лише на Strings, тож чому я хочу використовувати сканер для рядка? Чи сканер просто призначений для купівлі-розкрутки для розщеплення?

Відповіді:


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 (). Якщо вам НЕ ПОТРІБНО використовувати регулярні вирази, НЕ!
Alex Worden

Я просто використовував Scannerдля виявлення нових символів рядка в заданому String. Оскільки нові символи рядків можуть змінюватись від платформи до платформи (дивіться на Patternjavadoc!), А рядок введення НЕ гарантовано відповідає System.lineSeparator(), я вважаю Scannerбільш підходящим, оскільки він уже знає, на які нові символи рядка потрібно звертати увагу при виклику nextLine(). Бо String.splitмені доведеться подавати правильну схему регулярних виразів, щоб виявити роздільники рядків, які я не знаходжу в жодному стандартному місці (найкраще, що я можу зробити, - це скопіювати її з Scannerджерела класу).
ADTC

9

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

спліт з'явився на JDK 1.4. Повільніше, ніж токенізатор, але простіший у використанні, оскільки він може дзвонити з класу String.

Сканер з'явився на JDK 1.5. Це найбільш гнучка і заповнює тривалий пробіл в Java API для підтримки еквівалента відомого сімейства функцій scanf Cs.


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. Єдина перевага при спліт - це те, що ви отримуєте масив жетонів. Також ви можете використовувати будь-які регулярні вирази в розділеному вигляді. org.apache.commons.lang.StringUtils має метод розбиття, який працює набагато швидше, ніж будь-який із двох віз. StringTokenizer або String.split. Але використання процесора для всіх трьох майже однакове. Тому нам також потрібен менш інтенсивний процесор, який я досі не в змозі знайти.


3
Ця відповідь є трохи безглуздою. Ви кажете, що шукаєте щось швидше, але "менш інтенсивне процесора". Будь-яка програма виконується процесором. Якщо програма не використовує ваш процесор на 100%, вона повинна чекати чогось іншого, наприклад I / O. Це ніколи не повинно бути проблемою при обговоренні токенізації рядків, якщо тільки ви не здійснюєте прямий доступ до диска (чого ми, зокрема, не робимо тут).
Джолта

4

Нещодавно я робив кілька експериментів щодо поганої продуктивності String.split () у дуже чутливих до продуктивних ситуаціях ситуаціях. Ви можете вважати це корисним.

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

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


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.


-5

String.split () працює дуже добре, але має власні межі, як, наприклад, якщо ви хочете розділити рядок, як показано нижче, на основі символу одиночної або подвійної труби (|), це не працює. У цій ситуації ви можете використовувати StringTokenizer.

ABC | IJK


12
Насправді ви можете розділити свій приклад просто "ABC | IJK" .split ("\\ |");
Томо

"ABC || DEF ||" .split ("\\ |") насправді не працює, тому що він буде ігнорувати проміжні два порожніх значення, що робить синтаксичний аналіз більш складним, ніж повинен бути.
Арман
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.