W3docs

Как подсчитать слова в строке в Java

Правильный способ подсчёта слов в строке Java: trim и split по пробелам, регулярный Matcher и как избежать наивного разбиения по одному пробелу.

Подсчёт слов в строке кажется тривиальным — разбей по пробелам и посчитай элементы — но наивный вариант ломается, как только на входе появляются ведущие, замыкающие или повторяющиеся пробелы. В этой главе показаны идиоматические способы подсчёта слов в Java, разобраны краевые случаи, которые приводят к ошибкам, и объясняется, какой подход выбрать. Глава опирается на основы из Java Strings и String split and join.

Разбиение по пробелам (надёжный вариант по умолчанию)

Стандартный рецепт — обрезать строку, а затем разбить её по одному или нескольким пробельным символам с помощью регулярного выражения \s+:

String text = "  The quick   brown fox  ";
String trimmed = text.trim();
int words = trimmed.isEmpty() ? 0 : trimmed.split("\\s+").length;
System.out.println(words); // 4

Два момента делают это решение корректным. trim() удаляет ведущие и замыкающие пробелы, поскольку split("\\s+") на строке, которая начинается с пробела, порождает пустой ведущий элемент. Проверка isEmpty() защищает от пустого ввода: разбиение "" возвращает массив длиной 1, а не 0, поэтому без этой проверки пустая строка ошибочно сообщала бы об одном слове.

\\s — это строковый литерал Java для регулярного выражения \s, которое соответствует пробелам, символам табуляции и переводам строки. + означает «один или более», поэтому любая последовательность пробелов считается единственным разделителем.

Избегайте наивного разбиения по одному пробелу

Соблазнительно написать text.split(" ").length, но это разбивает строку по ровно одному пробелу и ломается на реальных данных:

String text = "  The quick   brown fox  ";
System.out.println(text.split(" ").length); // 8, not 4

Два ведущих пробела порождают два пустых ведущих элемента, а каждая серия из трёх внутренних пробелов добавляет ещё — в итоге массив выглядит как ["", "", "The", "quick", "", "", "brown", "fox"], восемь элементов вместо четырёх. (Java-метод split отбрасывает замыкающие пустые строки, именно поэтому два замыкающих пробела ничего не добавляют.) Каждый двойной пробел и каждый ведущий пробел завышают счёт. Разбиение по " " безопасно только тогда, когда вы заранее знаете, что на входе строка, разделённая одиночными пробелами без лишних — что редко гарантируется.

Подсчёт токенов с помощью регулярного Matcher

Вместо разбиения можно напрямую находить токены слов и подсчитывать совпадения. Это полностью обходит проблему пустых элементов:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

Matcher m = Pattern.compile("\\w+").matcher(text);
int count = 0;
while (m.find()) {
    count++;
}

\w+ соответствует последовательностям символов слова (буквы, цифры, знак подчёркивания). Поскольку он ищет слова, а не разделители, ведущие, замыкающие и повторяющиеся пробелы не имеют значения — дополнительная защита не нужна. Обратная сторона: \w не включает пунктуацию, поэтому дефисное слово "well-known" считается двумя токенами. Выбирайте шаблон в соответствии с вашим определением «слова».

Если вам нужно разбить на токены длинный документ или поток, а не одну строку, глава Java StringTokenizer рассматривает класс, созданный специально для разбиения текста на токены.

ПодходОбрабатывает лишние/краевые пробелыБезопасен для пустой строкиПримечание
split(" ")НетНетЛомается при повторяющихся пробелах
trim().split("\\s+")ДаС проверкой isEmpty()Вариант по умолчанию
Pattern \w+ matcherДаДа (возвращает 0)Делит по пунктуации

Полный рабочий пример

java— editable, runs on the server

Что важно вынести из запуска:

  • Naive split length: 8 доказывает, что split(" ") сильно завышает счёт, поскольку каждый ведущий пробел и каждый пробел внутри серии порождает лишний пустой элемент массива.
  • Whitespace split: 4 показывает, что trim().split("\\s+") сворачивает любую серию пробелов и даёт верный счёт.
  • Regex matcher: 4 подтверждает, что \w+ matcher приходит к тому же результату без какой-либо обрезки и дополнительных проверок.
  • Blank input words: 0 демонстрирует, почему важна проверка isEmpty() — без неё пустой ввод ошибочно сообщал бы об одном слове.
  • Все три корректных метода сходятся на 4, поэтому разница между ними — в надёжности и определении «слова», а не в результате на чистых данных.

Практика

Практика
Почему разбиение по одному пробелу завышает количество слов, когда строка содержит повторяющиеся или ведущие пробелы?
Почему разбиение по одному пробелу завышает количество слов, когда строка содержит повторяющиеся или ведущие пробелы?
Was this page helpful?