Регулярные выражения в Java: извлечение подстрок с Pattern и Matcher

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Java-разработчики, желающие улучшить свои навыки работы с регулярными выражениями
  • Студенты, изучающие курсы программирования на Java
  • Профессионалы, работающие с обработкой текстовых данных и парсингом

    Извлечение данных из текстовых источников — задача, с которой сталкивается каждый Java-разработчик. Будь то парсинг лог-файлов, обработка пользовательского ввода или работа с XML/JSON — без регулярных выражений эту битву не выиграть. Недостаточно знать, что они существуют — нужно мастерски владеть инструментарием для извлечения подстрок, иначе вместо элегантного кода получится нечитаемая конструкция из циклов и условий. Готовы превратить регулярные выражения из пугающего монстра в верного помощника? 🚀

Разрабатываете приложения на Java и хотите научиться эффективно манипулировать данными? На Курсе Java-разработки от Skypro мы посвящаем отдельный модуль регулярным выражениям и обработке строк. Наши студенты не просто изучают синтаксис, а решают реальные задачи парсинга и валидации данных, которые встречаются в промышленной разработке. Присоединяйтесь и превратите сложные регулярные выражения в свое конкурентное преимущество!

Основы регулярных выражений в Java: классы и шаблоны

Регулярные выражения — мощный инструмент, который при правильном использовании значительно упрощает обработку текста. В Java для работы с ними предусмотрен пакет java.util.regex, содержащий набор классов, превращающих работу с текстом в точную науку.

Центральное место в этом пакете занимают два класса: Pattern (скомпилированный шаблон регулярного выражения) и Matcher (движок для сопоставления шаблона с текстом). Взаимодействие этих классов определяет, насколько эффективно вы сможете извлекать нужные фрагменты данных из текста.

Артём Волков, ведущий Java-разработчик

Моё первое столкновение с регулярными выражениями в Java произошло при разработке системы логирования для высоконагруженного приложения. Нужно было извлекать из логов определённые паттерны ошибок для последующего анализа.

Вначале я наивно пытался использовать примитивные методы String: indexOf(), substring(), цепочки if-else. Код быстро превратился в неподдерживаемый монстр из 200+ строк. После изучения API регулярных выражений весь этот код сократился до 15 строк. Более того, производительность выросла на порядок — скомпилированные паттерны работают намного быстрее самописных парсеров.

Сейчас для меня регулярные выражения — это первый инструмент, к которому я обращаюсь при необходимости извлечения данных из текста. Они существенно снижают когнитивную нагрузку и делают код более декларативным.

Прежде чем перейти к практике, важно понять основные элементы синтаксиса регулярных выражений в Java:

Элемент Описание Пример
^ Начало строки ^Hello — строка начинается с "Hello"
$ Конец строки world$ — строка заканчивается на "world"
. Любой одиночный символ h.t — совпадает с "hat", "hot", "hit" и т.д.
\d Цифровой символ \d{3} — три цифры подряд
\w Буквенный, цифровой символ или _ \w+ — одно или более слово
Один из перечисленных символов [aeiou] — любая гласная
( ) Группировка для извлечения (abc) — группа "abc"
* 0 или более повторений \d* — ноль или более цифр
+ 1 или более повторений \d+ — одна или более цифр
? 0 или 1 повторение colou?r — "color" или "colour"

В Java есть особенность: поскольку обратная косая черта () в строковых литералах имеет специальное значение, для записи регулярного выражения содержащего , необходимо использовать двойную обратную косую черту (\). Например, чтобы найти цифровой символ, нужно писать "\d" вместо "\d".

Важно также понимать флаги компиляции регулярных выражений. Вот некоторые из них:

  • Pattern.CASE_INSENSITIVE — игнорирует регистр при сопоставлении
  • Pattern.MULTILINE — меняет поведение ^ и $, чтобы они соответствовали началу и концу каждой строки
  • Pattern.DOTALL — позволяет точке (.) соответствовать любому символу, включая перевод строки
  • Pattern.UNICODE_CASE — учитывает Unicode при сопоставлении без учета регистра

Создание шаблона выглядит так:

Java
Скопировать код
Pattern pattern = Pattern.compile("regex", Pattern.CASE_INSENSITIVE);

Понимание этих основ — первый шаг к мастерству работы с регулярными выражениями в Java. Теперь перейдем к практическим аспектам использования классов Pattern и Matcher. 💡

Пошаговый план для смены профессии

Pattern и Matcher: основные инструменты для подстрок

Классы Pattern и Matcher — краеугольный камень работы с регулярными выражениями в Java. Pattern представляет скомпилированный шаблон регулярного выражения, а Matcher применяет этот шаблон к конкретной строке и предоставляет методы для работы с найденными совпадениями.

Классический паттерн работы с этими классами выглядит следующим образом:

Java
Скопировать код
// Компилируем регулярное выражение в объект Pattern
Pattern pattern = Pattern.compile("\\b(\\w+)@(\\w+\\.[a-z]{2,})\\b");

// Создаем объект Matcher, применяя шаблон к строке
Matcher matcher = pattern.matcher("Contact us: support@example.com or sales@company.org");

// Используем методы Matcher для работы с совпадениями
while (matcher.find()) {
System.out.println("Full match: " + matcher.group(0));
System.out.println("Username: " + matcher.group(1));
System.out.println("Domain: " + matcher.group(2));
}

Компиляция регулярного выражения — важный этап, который не следует недооценивать. Компиляция преобразует строковое представление регулярного выражения в оптимизированную структуру данных, которую Java может эффективно использовать для поиска совпадений. Если вы планируете использовать одно и то же регулярное выражение многократно, скомпилируйте его один раз и переиспользуйте объект Pattern — это существенно улучшит производительность.

Для сравнения подходов, рассмотрим разницу между компиляцией регулярного выражения каждый раз и его повторным использованием:

Аспект Многократная компиляция Однократная компиляция
Код
Java
Скопировать код

|

Java
Скопировать код

|

| Производительность при 1000 строках | ~500-1000 мс | ~50-100 мс |

| Нагрузка на GC | Высокая | Низкая |

| Читаемость | Выше (короче запись) | Ниже (требуется больше кода) |

| Подходит для | Редкого, одноразового использования | Частого использования в циклах |

Объект Matcher предоставляет ряд важных методов для работы с найденными совпадениями:

  • find() — ищет следующее совпадение с шаблоном в строке
  • matches() — проверяет, соответствует ли вся строка шаблону
  • lookingAt() — проверяет, соответствует ли начало строки шаблону
  • groupCount() — возвращает количество групп захвата в шаблоне
  • group(int) — возвращает подстроку, соответствующую указанной группе захвата
  • start(int) и end(int) — возвращают индексы начала и конца совпадения для указанной группы
  • replaceFirst(String) и replaceAll(String) — заменяют совпадения указанной строкой

Понимание разницы между методами matches(), find() и lookingAt() критически важно:

Java
Скопировать код
Pattern pattern = Pattern.compile("cat");
Matcher matcher1 = pattern.matcher("cat"); // matches() вернет true
Matcher matcher2 = pattern.matcher("concatenate"); // matches() вернет false, find() вернет true
Matcher matcher3 = pattern.matcher("cathedral"); // matches() вернет false, lookingAt() вернет false, find() вернет true

Метод matches() проверяет, соответствует ли вся строка шаблону, в то время как find() ищет шаблон в любом месте строки. Метод lookingAt() проверяет, соответствует ли шаблону начало строки.

Еще один полезный паттерн использования — применение статического метода Pattern.matches() для быстрой проверки соответствия строки регулярному выражению:

Java
Скопировать код
if (Pattern.matches("\\d{3}-\\d{2}-\\d{4}", "123-45-6789")) {
// Действия с правильным форматом SSN
}

Однако помните о производительности — этот удобный метод компилирует регулярное выражение каждый раз, поэтому не используйте его в циклах или часто вызываемых методах. 🔍

Методы извлечения подстрок через group() и find()

Извлечение подстрок — одна из ключевых операций при работе с регулярными выражениями. В Java это делается преимущественно через группы захвата (capturing groups) и методы group() и find() класса Matcher.

Группы захвата в регулярных выражениях определяются с помощью круглых скобок (). Каждая такая группа сохраняет найденную подстроку, которую позже можно извлечь. Нумерация групп начинается с 1, при этом группа 0 всегда представляет собой всё найденное совпадение.

Java
Скопировать код
Pattern pattern = Pattern.compile("(\\d{3})-(\\d{3})-(\\d{4})");
Matcher matcher = pattern.matcher("Phone: 555-123-4567");

if (matcher.find()) {
String fullMatch = matcher.group(0); // "555-123-4567"
String areaCode = matcher.group(1); // "555"
String exchange = matcher.group(2); // "123"
String lineNumber = matcher.group(3); // "4567"

System.out.println("Full number: " + fullMatch);
System.out.println("Area code: " + areaCode);
}

Метод find() ищет следующее совпадение с шаблоном в строке и возвращает boolean: true, если совпадение найдено, и false в противном случае. Это позволяет последовательно перебирать все совпадения в цикле:

Java
Скопировать код
Pattern pattern = Pattern.compile("\\b\\w+\\b");
Matcher matcher = pattern.matcher("Java is a programming language");

// Находим все слова в строке
while (matcher.find()) {
System.out.println("Found word: " + matcher.group());
System.out.println("Position: " + matcher.start() + "-" + matcher.end());
}

Важно отметить, что после нахождения совпадения с помощью find(), объект Matcher запоминает состояние и при следующем вызове find() будет искать следующее совпадение с позиции после предыдущего. Это создает своеобразный "курсор" в строке.

Методы start() и end() возвращают индексы начала и конца найденного совпадения (или указанной группы), что может быть полезно для дополнительной обработки:

Java
Скопировать код
Pattern pattern = Pattern.compile("(\\w+)@(\\w+\\.\\w+)");
Matcher matcher = pattern.matcher("Contact john.doe@example.com for details");

if (matcher.find()) {
String email = matcher.group(0);
String username = matcher.group(1);
String domain = matcher.group(2);

int startPos = matcher.start();
int endPos = matcher.end();

System.out.println("Email found at position " + startPos + "-" + endPos + ": " + email);
System.out.println("Username: " + username);
System.out.println("Domain: " + domain);
}

Для более сложных случаев, когда требуется извлечь множество подстрок или обрабатывать группы условно, полезно использовать именованные группы захвата, доступные начиная с Java 7:

Java
Скопировать код
Pattern pattern = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
Matcher matcher = pattern.matcher("Date: 2023-11-15");

if (matcher.find()) {
String year = matcher.group("year"); // "2023"
String month = matcher.group("month"); // "11"
String day = matcher.group("day"); // "15"

System.out.println("Year: " + year);
System.out.println("Month: " + month);
System.out.println("Day: " + day);
}

Если нужно найти все совпадения и сохранить их для последующей обработки, удобно использовать стрим API, доступный с Java 8:

Java
Скопировать код
Pattern pattern = Pattern.compile("\\b\\w{5,}\\b");
String text = "Java developers often work with regular expressions";

// Находим все слова длиннее 4 букв
List<String> longWords = pattern.matcher(text)
.results()
.map(MatchResult::group)
.collect(Collectors.toList());

System.out.println(longWords); // [developers, often, regular, expressions]

Помните о некоторых типичных ловушках при использовании этих методов:

  • Вызов group() без предварительного успешного вызова find() приведет к IllegalStateException
  • Вызов group(n), где n больше, чем количество групп захвата, приведет к IndexOutOfBoundsException
  • Если группа не участвовала в совпадении (например, в случае альтернативы), group(n) вернет null

Использование методов find() и group() дает мощные инструменты для извлечения данных из текста, делая регулярные выражения незаменимым инструментом в арсенале Java-разработчика. 🔎

Практические кейсы использования регулярных выражений

Теоретические знания приобретают смысл только в контексте реальных задач. Рассмотрим несколько практических кейсов, где регулярные выражения демонстрируют свою мощь при извлечении подстрок в Java-приложениях.

Максим Игнатьев, разработчик систем обработки данных

В проекте для телеком-оператора нам требовалось анализировать огромные объёмы лог-файлов, извлекая информацию о звонках и SMS. Один запрос требовал извлечения данных о международных звонках в определённые страны.

Первоначально наша команда использовала цепочку методов String.split() и условных операторов, что приводило к сложночитаемому коду и частым ошибкам при изменении формата логов. После рефакторинга с использованием регулярных выражений кодовая база сократилась на 40%.

Ключевым моментом стало использование именованных групп захвата для извлечения кодов стран, длительности разговора и статусов вызовов. Это не только сделало код более читаемым, но и повысило производительность обработки на 30%, что критично при анализе терабайтов данных.

Java
Скопировать код
Pattern callPattern = Pattern.compile(
"CALL\\s+(?<timestamp>\\d{14})\\s+" +
"(?<sourceNumber>\\+\\d{10,15})\\s+" +
"(?<targetNumber>\\+(?<countryCode>\\d{1,3})\\d{8,12})\\s+" +
"(?<duration>\\d+)\\s+(?<status>\\w+)"
);

Matcher matcher = callPattern.matcher(logLine);
if (matcher.find() && 
targetCountries.contains(matcher.group("countryCode"))) {
CallRecord record = new CallRecord(
matcher.group("timestamp"),
matcher.group("sourceNumber"),
matcher.group("targetNumber"),
Integer.parseInt(matcher.group("duration")),
matcher.group("status")
);
internationalCalls.add(record);
}

Этот подход позволил нам быстро адаптироваться к изменениям в формате логов и легко расширять функциональность системы анализа.

Ниже приведены практические примеры, демонстрирующие возможности регулярных выражений в типичных задачах разработки.

1. Валидация и извлечение данных из email-адресов

Java
Скопировать код
// Регулярное выражение для проверки и извлечения частей email-адреса
Pattern emailPattern = Pattern.compile("^([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+)\\.([A-Za-z]{2,6})$");
Matcher matcher = emailPattern.matcher("user.name+tag@example.com");

if (matcher.matches()) {
String username = matcher.group(1); // user.name+tag
String domain = matcher.group(2); // example
String tld = matcher.group(3); // com

System.out.println("Email is valid");
System.out.println("Username: " + username);
System.out.println("Domain: " + domain);
System.out.println("TLD: " + tld);
}

2. Парсинг и извлечение данных из HTML

Java
Скопировать код
String html = "<div class='content'><h1>Title</h1><p>Paragraph with <a href='https://example.com'>link</a></p></div>";

// Извлечение всех ссылок
Pattern linkPattern = Pattern.compile("<a\\s+[^>]*href=['\"]([^'\"]+)['\"][^>]*>([^<]+)</a>");
Matcher linkMatcher = linkPattern.matcher(html);

while (linkMatcher.find()) {
String url = linkMatcher.group(1);
String linkText = linkMatcher.group(2);
System.out.println("Link: " + linkText + " -> " + url);
}

// Извлечение заголовка
Pattern titlePattern = Pattern.compile("<h1>([^<]+)</h1>");
Matcher titleMatcher = titlePattern.matcher(html);

if (titleMatcher.find()) {
System.out.println("Title: " + titleMatcher.group(1));
}

3. Обработка форматированных строк данных (CSV, логи)

Java
Скопировать код
// Парсинг строки CSV с учетом кавычек
String csvLine = "John,\"Doe, Jr.\",\"123 Main St, Apt 4\",New York,10001";

Pattern csvPattern = Pattern.compile("(?:^|,)(?:\"([^\"]*)\"|([^,]*))");
Matcher csvMatcher = csvPattern.matcher(csvLine);

List<String> fields = new ArrayList<>();
while (csvMatcher.find()) {
String field = csvMatcher.group(1) != null ? csvMatcher.group(1) : csvMatcher.group(2);
fields.add(field);
}

System.out.println("CSV fields: " + fields);

4. Извлечение и обработка дат из текста

Java
Скопировать код
String text = "Meeting scheduled for 2023-11-15 at 14:30. Follow-up on 2023-12-01.";

// Находим все даты в формате YYYY-MM-DD
Pattern datePattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher dateMatcher = datePattern.matcher(text);

List<LocalDate> dates = new ArrayList<>();
while (dateMatcher.find()) {
int year = Integer.parseInt(dateMatcher.group(1));
int month = Integer.parseInt(dateMatcher.group(2));
int day = Integer.parseInt(dateMatcher.group(3));

LocalDate date = LocalDate.of(year, month, day);
dates.add(date);
}

System.out.println("Extracted dates: " + dates);

5. Анализ и извлечение данных из URL

Java
Скопировать код
String url = "https://www.example.com:8080/path/to/resource?param1=value1&param2=value2#section";

Pattern urlPattern = Pattern.compile(
"^(https?)://([^:/]+)(?::(\\d+))?(/[^?#]*)?(?:\\?([^#]*))?(?:#(.*))?$"
);
Matcher urlMatcher = urlPattern.matcher(url);

if (urlMatcher.matches()) {
String protocol = urlMatcher.group(1); // https
String host = urlMatcher.group(2); // www.example.com
String port = urlMatcher.group(3); // 8080
String path = urlMatcher.group(4); // /path/to/resource
String query = urlMatcher.group(5); // param1=value1&param2=value2
String fragment = urlMatcher.group(6); // section

System.out.println("Protocol: " + protocol);
System.out.println("Host: " + host);
System.out.println("Port: " + (port != null ? port : "default"));

// Парсинг query параметров
if (query != null) {
Pattern paramPattern = Pattern.compile("([^&=]+)=([^&=]*)");
Matcher paramMatcher = paramPattern.matcher(query);

Map<String, String> params = new HashMap<>();
while (paramMatcher.find()) {
params.put(paramMatcher.group(1), matcher.group(2));
}

System.out.println("Query parameters: " + params);
}
}

Эти примеры демонстрируют, как регулярные выражения могут элегантно решать задачи, которые в противном случае потребовали бы сложного императивного кода. Однако помните, что не всё следует решать через регулярные выражения — для особо сложных случаев, таких как полноценный парсинг HTML или XML, лучше использовать специализированные библиотеки. 🛠️

Оптимизация работы с подстроками: лучшие практики

Эффективное использование регулярных выражений требует не только правильного синтаксиса, но и понимания нюансов производительности. Неоптимальные регулярные выражения могут привести к значительному замедлению приложения, особенно при работе с большими объемами текста. Рассмотрим ключевые принципы оптимизации.

1. Предкомпиляция шаблонов

Одно из главных правил оптимизации — компилируйте шаблоны один раз и переиспользуйте их. Это особенно важно в циклах и часто вызываемых методах:

Java
Скопировать код
// Неоптимальный подход
public boolean validateEmails(List<String> emails) {
for (String email : emails) {
if (!email.matches("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$")) {
return false;
}
}
return true;
}

// Оптимальный подход
private static final Pattern EMAIL_PATTERN = 
Pattern.compile("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$");

public boolean validateEmails(List<String> emails) {
for (String email : emails) {
if (!EMAIL_PATTERN.matcher(email).matches()) {
return false;
}
}
return true;
}

Разница в производительности может достигать порядка величины при обработке больших списков.

2. Избегайте жадных квантификаторов

Жадные квантификаторы (*, +, {n,m}) пытаются захватить как можно больше символов, что может привести к неожиданным результатам и снижению производительности. Используйте ленивые квантификаторы (*?, +?, {n,m}?) там, где это уместно:

Java
Скопировать код
String html = "<div>First</div><div>Second</div>";

// Жадный квантификатор – захватит всё от первого открывающего до последнего закрывающего тега
Pattern greedyPattern = Pattern.compile("<div>(.*)</div>");
Matcher greedyMatcher = greedyPattern.matcher(html);
if (greedyMatcher.find()) {
System.out.println(greedyMatcher.group(1)); // Output: First</div><div>Second
}

// Ленивый квантификатор – захватит минимально возможную строку
Pattern lazyPattern = Pattern.compile("<div>(.*?)</div>");
Matcher lazyMatcher = lazyPattern.matcher(html);
while (lazyMatcher.find()) {
System.out.println(lazyMatcher.group(1)); // Output: First, затем Second
}

3. Используйте кэширование результатов для тяжелых операций

Если вы выполняете сложные операции с результатами регулярных выражений, стоит кэшировать эти результаты:

Java
Скопировать код
// Класс для кэширования результатов извлечения данных
public class EmailParser {
private static final Pattern EMAIL_PATTERN = 
Pattern.compile("^([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+)\\.([A-Za-z]{2,6})$");

private final Map<String, EmailComponents> cache = new ConcurrentHashMap<>();

public EmailComponents parse(String email) {
return cache.computeIfAbsent(email, this::doParse);
}

private EmailComponents doParse(String email) {
Matcher matcher = EMAIL_PATTERN.matcher(email);
if (matcher.matches()) {
return new EmailComponents(
matcher.group(1),
matcher.group(2),
matcher.group(3)
);
}
throw new IllegalArgumentException("Invalid email: " + email);
}

// Класс для хранения компонентов email
public static class EmailComponents {
private final String username;
private final String domain;
private final String tld;

// конструктор, геттеры и т.д.
}
}

4. Выбор правильных конструкций

Некоторые регулярные конструкции работают быстрее других. Сравним различные подходы:

Задача Менее эффективно Более эффективно Почему
Поиск конкретного слова text.matches(".*word.*") text.contains("word") Встроенный метод оптимизирован и не требует регулярных выражений
Проверка начала строки text.matches("^prefix.*") text.startsWith("prefix") Прямая проверка без регулярных выражений
Поиск одного из множества символов pattern = "a|b|c|d|e" pattern = "[a-e]" Символьные классы эффективнее альтернатив
Повторения pattern = "\d\d\d\d" pattern = "\d{4}" Квантификаторы более компактны и эффективны
Игнорирование групп захвата pattern = "(pattern)" pattern = "(?:pattern)" Незахватывающие группы не требуют хранения промежуточных результатов

5. Предотвращение катастрофического отката

Некоторые регулярные выражения могут привести к "катастрофическому откату" (catastrophic backtracking), когда движок регулярных выражений перебирает экспоненциальное количество комбинаций:

Java
Скопировать код
// Потенциально опасное регулярное выражение
Pattern dangerousPattern = Pattern.compile("(a+)+b");

// При проверке строки "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac" 
// может произойти зависание из-за экспоненциального количества комбинаций

Чтобы избежать этого:

  • Избегайте вложенных повторяющихся групп
  • Используйте атомарные группы (?&gt;...) для предотвращения отката
  • Применяйте possessive квантификаторы (*+, ++, {n,m}+), которые никогда не откатываются
Java
Скопировать код
// Безопасная альтернатива
Pattern safePattern = Pattern.compile("a++b");

6. Оптимизация конкретных паттернов

Для часто встречающихся задач существуют оптимизированные паттерны:

Java
Скопировать код
// Извлечение всех целых чисел из текста
// Неоптимально: \\d+ может совпадать с частями чисел
String text = "There are 123 apples and 456 oranges";
Pattern numberPattern = Pattern.compile("\\b\\d+\\b");
Matcher matcher = numberPattern.matcher(text);
while (matcher.find()) {
System.out.println("Number: " + matcher.group());
}

7. Мониторинг производительности

Для критичных по производительности приложений стоит измерить время выполнения регулярных выражений:

Java
Скопировать код
public void testRegexPerformance() {
Pattern pattern = Pattern.compile("complex regex here");
String testString = "large text to test against";

long startTime = System.nanoTime();
Matcher matcher = pattern.matcher(testString);
boolean found = matcher.find();
long endTime = System.nanoTime();

System.out.printf("Matching took %d ms, result: %b%n", 
(endTime – startTime) / 1_000_000, found);
}

Следуя этим принципам, вы сможете использовать всю мощь регулярных выражений в Java без ущерба для производительности вашего приложения. Помните: правильно оптимизированные регулярные выражения — это не только чистый код, но и эффективное использование ресурсов. 🚀

Регулярные выражения — незаменимый инструмент в арсенале Java-разработчика. Мы разобрали основные методы извлечения подстрок через группы захвата, узнали о тонкостях работы с Pattern и Matcher, а также рассмотрели практические кейсы и принципы оптимизации. Мастерство в этой области приходит с практикой — экспериментируйте, тестируйте производительность и постоянно совершенствуйте свои регулярные выражения. Помните: хорошее регулярное выражение — не только то, которое работает, но и то, которое другие разработчики смогут понять и поддерживать.

Загрузка...