Регулярные выражения

Regex описывает шаблон текста. Он удобен для локальной структуры строки, но плохо заменяет parser вложенного языка: JSON, HTML или SQL лучше разбирать специализированным инструментом.

Java API

Pattern orderPattern = Pattern.compile(
        "^ORD-(?<year>\\d{4})-(?<number>\\d{6})$"
);
 
Matcher matcher = orderPattern.matcher("ORD-2026-000042");
if (matcher.matches()) {
    String year = matcher.group("year");
    String number = matcher.group("number");
}

В Java matches() требует совпадения всей последовательности, find() ищет следующий фрагмент, lookingAt() — совпадение от начала. Обратный slash сначала обрабатывает Java string literal, поэтому regex \d записывается "\\d".

Основные конструкции

КонструкцияЗначение
.любой символ, с учётом flags
\d, \w, \sкласс цифр, word, whitespace
[...], [^...]класс и отрицание
*, +, ?, {m,n}квантификаторы
(...), (?:...)capturing и non-capturing group
^, $границы строки/line в зависимости от mode
(?=...), (?<=...)lookahead и lookbehind

Greedy .* берёт максимально возможный фрагмент, reluctant .*? — минимальный, possessive .*+ не отдаёт символы при backtracking.

Безопасное динамическое значение

Если пользовательский текст должен быть literal, его нельзя вставлять в regex напрямую:

Pattern p = Pattern.compile(
        "^" + Pattern.quote(userPrefix) + "\\d+$"
);

Для replacement используется отдельная escaping-модель; динамическую replacement-строку защищает Matcher.quoteReplacement.

Поиск одиночного переноса внутри абзаца

(?<=\S)\R(?=\S)

Он находит line break между непробельными символами. Перед массовой заменой проверяю sample: перенос после точки и перенос в code block могут иметь разный смысл. Для Markdown лучше ограничивать область обычными абзацами, а не применять pattern ко всему vault.

Производительность

Backtracking на шаблонах вроде (a+)+$ и длинной почти подходящей строке может расти взрывно. Защита:

  • ограничить длину input;
  • избегать вложенных неоднозначных quantifiers;
  • применять atomic/possessive groups осознанно;
  • компилировать постоянный Pattern один раз;
  • измерять worst-case, если input недоверенный.

Типичные сбои

  • Использовать matches(), ожидая поиск подстроки.
  • Забыть двойное escaping в Java.
  • Передать пользовательский fragment как готовый regex.
  • Парсить вложенную структуру одним огромным pattern.
  • Получить catastrophic backtracking на почти совпавшей строке.
  • Считать \w точным определением допустимого логина.

Вопросы для собеседования

Чем matches отличается от find? Первый сопоставляет весь input, второй ищет следующий фрагмент.

Зачем non-capturing group (?:...)? Группировать без сохранения отдельного результата.

Почему в Java пишут "\\d+"? Один backslash нужен Java literal, второй остаётся regex engine.

Что такое greedy quantifier? Он сначала захватывает максимально возможный фрагмент, затем отдаёт часть при backtracking.

Как вставить literal в pattern? Через Pattern.quote.