String.split: примеры (JAVA)

Примеры и нюансы использования String.split
Раздел: Парсинг
String.split(String regex): String[]

Описание метода String.split

Метод String.split применяется для разделения строки на фрагменты по заданному разделителю, заданному в виде регулярного выражения. В стандартной библиотеке определены две сигнатуры:

  • public String[] split(String regex) - разделение по регулярному выражению без явного ограничения количества частей (эквивалентно вызову с limit = 0).
  • public String[] split(String regex, int limit) - разделение по регулярному выражению с указанием предельного числа элементов в возвращаемом массиве.

Параметры и поведение:

  • regex - регулярное выражение, описывающее разделитель. При необходимости разделитель как литерал нужно экранировать или использовать Pattern.quote.
  • limit > 0 - в результате не более limit элементов. При достижении предела оставшаяся часть строки помещается в последний элемент (она может содержать разделители).
  • limit == 0 - поведение по умолчанию: результат содержит все элементы, но ведущие и внутренние пустые строки сохраняются, а конечные пустые строки удаляются.
  • limit < 0 - нет ограничения по количеству элементов, все возможные пустые строки, в том числе конечные, сохраняются.

Возвращаемое значение: массив String[] с фрагментами. В редких случаях массив может быть пустым (например, при разбиении пустой строки с регулярным выражением, совпадающим со всем содержимым, или при определенных комбинациях regex и limit).

Исключения и замечания:

  • Если строка, на которой вызывается метод, равна null, возникает NullPointerException.
  • Если передан некорректный шаблон регулярного выражения, возникает PatternSyntaxException.
  • Каждый вызов компилирует регулярное выражение заново. При частом использовании с одним и тем же шаблоном рекомендуется заранее компилировать Pattern и использовать pattern.split() для повышения производительности.
  • Если регулярное выражение содержит захватывающие группы (parentheses), то текст, соответствующий этим группам, включается в результирующий массив между частями (см. примеры далее).

Короткие примеры использования

Примеры демонстрируют обычные варианты работы с различными значениями regex и limit.

Разделение по пробелу:

String s = "one two three";
String[] parts = s.split("\\s+");
System.out.println(Arrays.toString(parts));
[one, two, three]

Разделение по точке (экранирование):

String s = "a.b.c";
String[] p = s.split("\\.");
System.out.println(Arrays.toString(p));
[a, b, c]

limit > 0: объединение остатка в последний элемент:

String s = "a,b,c,d";
String[] p = s.split(",", 3);
System.out.println(Arrays.toString(p));
[a, b, c,d]

limit == 0: удаление конечных пустых строк:

String s = "a,,";
String[] p = s.split(",", 0);
System.out.println(Arrays.toString(p));
[a]

limit < 0: сохранение конечных пустых строк:

String s = "a,,";
String[] p = s.split(",", -1);
System.out.println(Arrays.toString(p));
[a, , ]

Регулярное выражение с захватывающей группой (группы появляются в результате):

String s = "a-1-b";
String[] p = s.split("(-)" );
System.out.println(Arrays.toString(p));
[a, -, 1, -, b]

Альтернативы в Java

  • Pattern.split(CharSequence) - предпочтителен при многократном использовании одного шаблона, так как регулярное выражение компилируется один раз.
  • Scanner.useDelimiter - удобен при парсинге потоков и чтении токенов с различными типами, не возвращает массив сразу, а дает итеративный доступ.
  • StringTokenizer - устаревший класс для простых разделителей без регулярных выражений; быстрее для простых задач, но не поддерживает regex и рекомендован к ограниченному использованию.
  • Guava Splitter - удобен для цепочек фильтрации, пропуска пустых строк, тримминга и настройки поведения; не использует regex по умолчанию, но предоставляет гибкие варианты.
  • Apache Commons StringUtils.split - выполняет простое разделение без regex, полезен для простых разделителей и безопасного поведения при null.

Выбор зависит от задачи: для многократного применения одного regex - Pattern; для потоковой обработки - Scanner; для удобного API с фильтрацией - Guava; для простых литералов - StringUtils или String.split с Pattern.quote.

Аналоги в других языках

Краткие сравнения с примерами работы и отличиями от Java.

PHP:

// explode (литеральный разделитель)
$parts = explode(",", "a,b,c");
print_r($parts);

// preg_split (регекс)
$parts = preg_split('/\\s+/', "one two  three");
print_r($parts);
Array ( [0] => a [1] => b [2] => c )
Array ( [0] => one [1] => two [2] => three )

Особенность: explode работает с литеральным разделителем и быстрее для простых случаев, preg_split использует PCRE.

JavaScript:

const s = "a.b.c";
console.log(s.split('.'));
console.log("one  two".split(/\s+/));
[ 'a', 'b', 'c' ]
[ 'one', 'two' ]

Отличие: JS split принимает строку или RegExp; нет варианта limit == 0 с удалением конечных пустых элементов - есть параметр limit как положительное число.

Python:

s = "one two  three"
print(s.split())          # split по пробелам, сжатие пробелов
import re
print(re.split(r"\s+", s))
['one', 'two', 'three']
['one', 'two', 'three']

Особенность: str.split() при вызове без аргумента сжимает пробелы; для regex используется re.split.

SQL:

-- SQL Server
SELECT value FROM STRING_SPLIT('a,b,c', ',');

-- PostgreSQL
SELECT regexp_split_to_array('a,b,c', ',');
a
b
c
{a,b,c}

Отличия: реализация и порядок строк зависят от СУБД; многие реализации не гарантируют порядка (SQL Server до определённых версий).

C#:

var s = "a,b,c";
var p = s.Split(',');
// Regex.Split
var r = System.Text.RegularExpressions.Regex.Split("one  two", "\\s+");
Console.WriteLine(string.Join("|", p));
a|b|c

Отличие: string.Split имеет перегрузки с удалением пустых элементов и с массивом разделителей; Regex.Split использует регулярные выражения.

Lua:

local s = "a,b,c"
local t = {}
for part in string.gmatch(s, "[^,]+") do table.insert(t, part) end
print(table.concat(t, '|'))
a|b|c

Отличие: в стандартной библиотеке нет готового split - используется pattern matching и gmatch.

Go:

import "strings"
fmt.Println(strings.Split("a,b,c", ","))
import "regexp"
r := regexp.MustCompile(`\s+`)
fmt.Println(r.Split("one  two", -1))
[a b c]
[one two]

Отличие: strings.Split использует литерал, regexp.Split использует компилируемый regex; поведение limit аналогично Java (в Go вторым аргументом -1 сохраняет все элементы).

Kotlin:

val s = "a,b,c"
println(s.split(","))
println(s.split(Regex(","), limit = 2))
[a, b, c]
[a, b,c]

Отличие: Kotlin предоставляет перегрузки с Regex и удобные коллекции; семантика limit близка к Java.

Типичные ошибки и их проявления

  • Неэкранированный специальный символ regex - ожидание разделения по точке, но используется ситуация с любым символом:
    String s = "a.b.c";
    String[] p = s.split(".");
    System.out.println(Arrays.toString(p));
    []  // неожиданное поведение: регулярное выражение "." соответствует любому символу, результат пустой строкой-частями
    Рекомендация: использовать "\\." или Pattern.quote(".").
  • Непонимание поведения limit - удаление конечных пустых строк при limit == 0 или объединение остатка при limit > 0:
    System.out.println(Arrays.toString("a,,".split(",", 0)));
    System.out.println(Arrays.toString("a,,".split(",", -1)));
    [a]
    [a, , ]
  • NullPointerException при вызове на null:
    String s = null;
    s.split(",");
    Exception in thread "main" java.lang.NullPointerException
  • Игнорирование затрат на компиляцию regex при частых вызовах - заметное снижение производительности. Пример замены на Pattern.compile(...) и повторное использование:
  • Неожиданное включение текстов захваченных групп в результат при использовании округляющих скобок:
    String s = "a-1-b";
    System.out.println(Arrays.toString(s.split("(-)")));
    [a, -, 1, -, b]
    Это иногда вызывает лишние элементы в массиве.

Изменения и история

Метод String.split присутствует в Java с версии 1.4 и опирается на API регулярных выражений (класс Pattern). С течением времени сам метод существенно не менялся. Основные улучшения косвенно связаны с оптимизациями в движке регулярных выражений и с добавлением удобных альтернатив в более поздних версиях Java (например, String.lines() в Java 11 для разделения по строкам). В целом семантика split и значение параметра limit остались стабильными.

Расширенные и редкие сценарии использования

Ниже примеры, показывающие более тонкие моменты и советы по применению.

1) Повторное использование компилированного шаблона для производительности:

Пример java
Pattern p = Pattern.compile("\\s+");
for (String line : lines) {
    String[] parts = p.split(line); // быстрее, чем line.split("\\s+") в цикле
}
(нет текстового вывода, экономия на компиляции регулярных выражений)

2) Включение разделителя в результат через захватывающие группы:

Пример java
String s = "key1=1;key2=2";
String[] p = s.split("([=;])");
System.out.println(Arrays.toString(p));
[key1, =, 1, ;, key2, =, 2]

Полезно при необходимости сохранить разделитель или его тип между токенами.

3) Ограниченное разбиение при необходимости получить заголовок и тело:

Пример java
String s = "Subject: Title\r\n\r\nBody with \r\nlines";
String[] parts = s.split("\r\n\r\n", 2);
System.out.println("Header: " + parts[0]);
System.out.println("Body: " + parts[1]);
Header: Subject: Title
Body: Body with 
lines

4) Парсинг чисел и преобразование в поток:

Пример java
String s = "1, 2, 3,4";
int[] nums = Arrays.stream(s.split(",\\s*"))
    .mapToInt(Integer::parseInt).toArray();
System.out.println(Arrays.toString(nums));
[1, 2, 3, 4]

5) Разбиение с использованием lookaround для сохранения разделителя в виде метки, но без его включения в результаты:

Пример java
String s = "a; b, c|d";
// разделить, сохранив разделители как границы, но не включая их
String[] parts = s.split("(?<=[;,|])\\s*|\\s*(?=[;,|])");
System.out.println(Arrays.toString(parts));
[a;, b,, c|, d]

Композиция lookbehind / lookahead позволяет контролировать, какие символы остаются рядом с фрагментами.

6) Разделение по Unicode-классам, например по всем пунктуационным символам:

Пример java
String s = "word1,word2;word3.word4";
String[] parts = s.split("\\p{Punct}+");
System.out.println(Arrays.toString(parts));
[word1, word2, word3, word4]

7) Осторожность при попытке распарсить CSV с помощью простого split - пример ошибки и рекомендация:

Пример java
String csv = "a,\"b,b\",c";
System.out.println(Arrays.toString(csv.split(",")));
// Неправильно для полей в кавычках, лучше использовать CSV-парсер из библиотеки
[a, "b, b", c]  // на самом деле split даст [a, "b, b", c] только случайно; в сложных случаях будут ошибки

Замечание: для корректного разбора CSV с кавычками и экранированием рекомендуется использовать специализированные парсеры (OpenCSV, Apache Commons CSV).

8) Комбинация split и Stream API для сложных преобразований (фильтрация, приведение типов, агрегация):

Пример java
String s = " 1 , , 2 , 3 ,  " ;
List nums = Arrays.stream(s.split(","))
    .map(String::trim)
    .filter(t -> !t.isEmpty())
    .map(Integer::valueOf)
    .collect(Collectors.toList());
System.out.println(nums);
[1, 2, 3]

9) Разбиение с сохранением пустых токенов и последующая обработка:

Пример java
String s = ",a,,b,"
String[] p = s.split(",", -1);
System.out.println(Arrays.toString(p));
// Обработка: замена пустых значений на null
for (int i=0;i<p.length;i++) if (p[i].isEmpty()) p[i] = null;
System.out.println(Arrays.toString(p));
[, a, , b, ]
[null, a, null, b, null]

10) Использование split в рекурсивных или потоковых алгоритмах: при глубоких разбиениях лучше учитывать затраты на создание массивов и память; при необходимости использовать итеративные подходы или потоковую обработку с Matcher.

джава String.split function comments

En
String.split Разделяет строку по регулярному выражению