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