JavaRush /Java блог /Random UA /Регулярні вирази у Java (RegEx)

Регулярні вирази у Java (RegEx)

Стаття з групи Random UA
Регулярні висловлювання — тема, яку програмісти, навіть досвідчені, найчастіше відкладають потім. Однак більшості Java-розробників рано чи пізно доведеться зіткнутися з обробкою текстової інформації. Найчастіше - з операціями пошуку в тексті та редагуванням. Без регулярних виразів продуктивний і компактний програмний код, пов'язаний із обробкою текстів, просто немислимий. Тож вистачить відкладати, розберемося з «регулярками» зараз. Це не таке вже й складне завдання.

Що таке регулярне вираження RegEx?

Насправді регулярне вираз (RegEx в Java) – це шаблон пошуку рядка в тексті. Java вихідним поданням цього шаблону завжди є рядок, тобто об'єкт класу String. Однак не будь-який рядок може бути скомпільований у регулярний вираз, а лише той, який відповідає правилам написання регулярного виразу – синтаксису, визначеному у специфікації мови. Для написання регулярного виразу використовуються буквені та цифрові символи, а також метасимволи – символи, що мають спеціальне значення у синтаксисі регулярних виразів. Наприклад:
String regex = "java"; // шаблон рядка "java";
String regex = "\\d{3}"; // шаблон рядка із трьох цифрових символів;

Створення регулярних виразів у Java

Щоб створити RegEx в Java, потрібно зробити два простих кроки:
  1. написати його у вигляді рядка з урахуванням синтаксису регулярних виразів;
  2. скомпілювати цей рядок у регулярний вираз;
Робота з регулярними виразами у будь-якій Java-програмі починається зі створення об'єкта класу Pattern. Для цього необхідно викликати один із двох наявних у класі статичних методів compile. Перший метод приймає один аргумент – рядковий літерал регулярного виразу, а другий – плюс ще параметр, що включає режим порівняння шаблону з текстом:
public static Pattern compile (String literal)
public static Pattern compile (String literal, int flags)
Список можливих значень параметра flagsвизначено у класі Patternта доступний нам як статичні змінні класу. Наприклад:
Pattern pattern = Pattern.compile("java", Pattern.CASE_INSENSITIVE);//Пошук збігів з шаблоном буде здійснюватися без урахування регістру символів.
По суті, клас Pattern– це конструктор регулярних виразів. Під «капотом» метод compileвикликає закритий конструктор класу Patternстворення скомпаборованного уявлення. Такий спосіб створення екземпляра шаблону реалізований з метою створення його у вигляді незмінного об'єкта. Під час створення проводиться синтаксична перевірка регулярного висловлювання. За наявності помилок у рядку – генерується виняток PatternSyntaxException.

Синтаксис регулярних виразів

Синтаксис регулярних виразів ґрунтується на використанні символів <([{\^-=$!|]})?*+.>, які можна поєднувати з літерними символами. Залежно від ролі їх можна поділити на кілька груп:
1. Метасимволи для пошуку збігів меж рядків або тексту
Метасимвол Призначення
^ початок рядка
$ кінець рядка
\b межа слова
\B не межа слова
\A початок введення
\G кінець попереднього збігу
\Z кінець введення
\z кінець введення
2. Метасимволи для пошуку символьних класів
Метасимвол Призначення
\d цифровий символ
\D нецифровий символ
\s символ пропуску
\S непробільний символ
\w буквено-цифровий символ або знак підкреслення
\W будь-який символ, окрім буквеного, цифрового або знак підкреслення
. будь-який символ
3. Метасимволи для пошуку символів редагування тексту
Метасимвол Призначення
\t символ табуляції
\n символ нового рядка
\r символ повернення каретки
\f перехід на нову сторінку
0085 символ наступного рядка
\u 2028 символ поділу рядків
\u 2029 символ поділу абзаців
4. Метасимволи для групування символів
Метасимвол Призначення
[а Б В] будь-який із перерахованих (а,б, або в)
[^ Абв] будь-який, крім перерахованих (не а, б, в)
[a-zA-Z] злиття діапазонів (латинські символи від a до z без урахування регістру)
[ad[mp]] об'єднання символів (від a до d і від m до p)
[az&&[def]] перетин символів (символи d, e, f)
[az&&[^bc]] віднімання символів (символи a, dz)
5. Метасимволи для позначення кількості символів – квантифікатори. Квантифікатор завжди слідує після символу або групи символів.
Метасимвол Призначення
? один або відсутній
* нуль або більше разів
+ один або більше разів
{n} n раз
{n,} n разів і більше
{n, m} не менше n разів і не більше m разів

Жадібний режим квантифікатора

Особливістю квантифікаторів є можливість використання їх у різних режимах: жадібному, наджадібному та лінивому. Наджадібний режим включається додаванням символу « +» після квантифікатора, а лінивий – символу « ?». Наприклад:
"А.+а" // жадібний режим
"А.++а" // Над жадібний режим
"А.+?а" // лінивий режим
Спробуємо з прикладу цього шаблону розібратися у роботі квантифікаторів у різних режимах. За замовчуванням квантифікатор працює у жадібному режимі. Це означає, що він шукає максимально довгий збіг у рядку. В результаті виконання цього коду:
public static void main(String[] args) {
    String text = "Єгор Алла Олександр";
    Pattern pattern = Pattern.compile("А.+а");
    Matcher matcher = pattern.matcher(text);
    while (matcher.find()) {
        System.out.println(text.substring(matcher.start(), matcher.end()));
    }
}
ми отримаємо такий висновок: Алла Алекса Алгоритм пошуку за заданим шаблоном " А.+а", виконується в наступній послідовності:
  1. У заданому шаблоні перший символ – це символ літери А. Matcherзіставляє його з кожним символом тексту, починаючи з нульової позиції. На нульовій позиції в тексті знаходиться символ Е, тому Matcherперебирає послідовно символи в тексті, доки не зустріне збіг з шаблоном. У прикладі це символ на позиції №5.

    Регулярні вирази в Java - 2
  2. Після того, як знайдено збіг з першим символом шаблону, Matcherзвіряє відповідність з другим символом шаблону. У нашому випадку це символ « .», який означає будь-який символ.

    Регулярні вирази в Java - 3

    На шостій позиції – символ літери л. Вочевидь, він відповідає шаблону «будь-який символ».

  3. Matcherпереходить до перевірки наступного символу із шаблону. У нашому шаблоні він поставлений за допомогою квантифікатора « .+». Оскільки кількість повторень «будь-якого символу» у шаблоні – один і більше разів, Matcherбере по черзі наступний символ з рядка і перевіряє його на відповідність шаблону, доки буде виконуватися умова «будь-який символ», у нашому прикладі – до кінця рядка ( з поз. №7 - №18 тексту).

    Регулярні вирази в Java - 4

    Власне, Matcher, захоплює все рядок остаточно – у цьому таки виявляється його «жадібність».

  4. Після того як Matcherдійшов до кінця тексту і закінчив перевірку для частини шаблону « А.+», Matcher починає перевірку для частини шаблону, що залишилася - символ літери а. Оскільки текст у прямому напрямку закінчився, перевірка відбувається у зворотному напрямку, починаючи з останнього символу:

    Регулярні вирази в Java - 5
  5. Matcher«Пам'ятає» кількість повторень у шаблоні « .+», при якому він дійшов до кінця тексту, тому він зменшує кількість повторень на одиницю і перевіряє відповідність шаблону тексту, доки не буде знайдено збіг: Регулярні вирази в Java - 6

Надмірний режим квантифікатора

У наджадібному режимі робота матчера аналогічна механізму жадібного режиму. Відмінність у тому, що з захоплення тексту остаточно рядка пошук у зворотному напрямі немає. Тобто перші три етапи при наджадібному режимі будуть аналогічні жадібному режиму. Після захоплення всього рядка матчер додає залишок шаблону і порівнює із захопленим рядком. У нашому прикладі під час виконання методу main з шаблоном " А.++а" збігів не буде знайдено. Регулярні вирази в Java - 7

Лінивий режим квантифікатора

  1. У цьому режимі на початковому етапі, як і в жадібному режимі, шукається збіг з першим символом шаблону:

    Регулярні вирази в Java - 8
  2. Далі шукається збіг із наступним символом шаблону – будь-яким символом:

    Регулярні вирази в Java - 9
  3. На відміну від жадібного режиму, в лінивому шукається найкоротший збіг у тексті, тому після знаходження збігу з другим символом шаблону, який заданий точкою та відповідає символу на позиції №6 тексту, перевірятиме відповідність тексту Matcherзалишку шаблону – символу « а»

    Регулярні вирази в Java - 10
  4. Оскільки збіг із шаблоном у тексті не знайдено (на позиції №7 у тексті знаходиться символ « л»), Matcherдодає ще один «будь-який символ» у шаблоні, оскільки він заданий як один і більше разів, і знову порівнює шаблон з текстом на позиціях з №5 по №8:

    Регулярні вирази в Java - 11
  5. У нашому випадку знайдено збіг, але кінець тексту ще не досягнуто. Тому з позиції №9 перевірка починається з пошуку першого символу шаблону за аналогічним алгоритмом і далі повторюється до закінчення тексту.

    Регулярні вирази в Java - 12
В результаті роботи методу mainпри використанні шаблону А.+?ами отримаємо наступний результат: Алла Алекса Як видно з нашого прикладу, при використанні різних режимів квантифікатора для одного і того ж шаблону ми отримали різні результати. Тому необхідно враховувати цю особливість та вибирати потрібний режим залежно від бажаного результату під час пошуку.

Екранування символів у регулярних виразах

Оскільки регулярний вираз у Java, а точніше — його вихідне уявлення задається за допомогою рядкового літералу, необхідно враховувати правила специфікації Java, які стосуються рядкових літералів. Зокрема, символ зворотної косої риси « \» у рядкових літералах у вихідному коді Java інтерпретується як символ послідовності, що управляє, який попереджає компілятор, що наступний за ним символ — спеціальний і що його потрібно особливим чином інтерпретувати. Наприклад:
String s = "The root directory is \nWindows";//перенесення Windows на новий рядок
String s = "The root directory is \u00A7Windows";//вставка символу параграфа перед Windows
Тому в рядкових літералах, які описують регулярний вираз, і використовують символ « \» (наприклад, для метасимволів) його потрібно подвоювати , щоб компілятор байт-коду Java не інтерпретував його по-своєму. Наприклад:
String regex = "\\s"; // шаблон для пошуку символів пропуску
String regex = "\"Windows\""; // шаблон для пошуку рядка "Windows"
Подвійний символ зворотної косої риси також слід використовувати для екранування символів, які задіяні як спеціальні, якщо ми плануємо їх використовувати як «звичайні» символи. Наприклад:
String regex = "How\\?"; // шаблон пошуку рядка “How?”

Методи класу Pattern

У класі Patternє й інші методи для роботи з регулярними виразами: String pattern()- Повертає вихідне рядкове подання регулярного виразу, з якого був створений об'єкт Pattern:
Pattern pattern = Pattern.compile("abc");
System.out.println(Pattern.pattern())//"abc"
static boolean matches(String regex, CharSequence input)– дозволяє перевірити регулярний вираз, переданий у параметрі regex на відповідність тексту, переданому у параметрі input. Повертає: true – якщо текст відповідає шаблону; false – інакше; Приклад:
System.out.println(Pattern.matches("А.+а","Алла"));//true
System.out.println(Pattern.matches("А.+а","Єгор Алла Олександр"));//false
int flags()– повертає значення параметра flagsшаблону, які були встановлені під час його створення, або 0, якщо цей параметр не встановлено. Приклад:
Pattern pattern = Pattern.compile("abc");
System.out.println(pattern.flags());// 0
Pattern pattern = Pattern.compile("abc",Pattern.CASE_INSENSITIVE);
System.out.println(pattern.flags());// 2
String[] split(CharSequence text, int limit)– розбиває текст, переданий як параметр масив елементів String. Параметр limitвизначає граничну кількість збігів, які шукаються в тексті:
  • при limit>0- виконується пошук limit-1збігів;
  • при limit<0– виконується пошук усіх збігів у тексті
  • при limit=0– виконується пошук усіх збігів у тексті, у своїй порожні рядки наприкінці масиву відкидаються;
Приклад:
public static void main(String[] args) {
    String text = "Єгор Алла Анна";
    Pattern pattern = Pattern.compile("\\s");
    String[] strings = pattern.split(text,2);
    for (String s : strings) {
        System.out.println(s);
    }
    System.out.println("---------");
    String[] strings1 = pattern.split(text);
    for (String s : strings1) {
        System.out.println(s);
    }
}
Ще один метод класу для створення об'єкта розглянемо нижче Matcher.

Методи класу Matcher

Matcherє клас, з якого створюється об'єкт для пошуку збігів за шаблоном. Matcher- Це «пошуковик», «движок» регулярних виразів. Для пошуку йому треба дати дві речі: шаблон пошуку та «адресау», за якою шукати. Для створення об'єкта Matcherпередбачений наступний метод у класі Pattern: рublic Matcher matcher(CharSequence input) Як аргумент метод приймає послідовність символів, в якому буде здійснюватися пошук. Це об'єкти класів, що реалізують інтерфейс CharSequence. як аргумент можна передати як String, а й StringBuffer, StringBuilder, Segmentі CharBuffer. Шаблоном для пошуку є об'єкт класу Pattern, на якому викликається метод matcher. Приклад створення матчера:
Pattern p = Pattern.compile("a*b");// скомпілювали регулярне вираження на виставу
Matcher m = p.matcher("aaaaab");//Створабо пошуковик у тексті “aaaaab” за шаблоном "a*b"
Тепер за допомогою нашого «пошукача» ми можемо шукати збіги, дізнаватися про позицію збігу в тексті, замінювати текст за допомогою методів класу. Метод boolean find()шукає черговий збіг у тексті із шаблоном. За допомогою цього методу та оператора циклу можна проводити аналіз усього тексту за подієвою моделлю (здійснювати необхідні операції при настанні події – знаходженні збігу в тексті). Наприклад, за допомогою методів цього класу int start()можна int end()визначати позиції збігу в тексті, а за допомогою методів String replaceFirst(String replacement)і String replaceAll(String replacement)замінювати в тексті збіги на інший текст replacement. Приклад:
public static void main(String[] args) {
    String text = "Єгор Алла Анна";
    Pattern pattern = Pattern.compile("А.+?а");

    Matcher matcher = pattern.matcher(text);
    while (matcher.find()) {
        int start=matcher.start();
        int end=matcher.end();
        System.out.println("Знайдено збіг" + text.substring(start,end) + " с "+ start + "за" + (end-1) + "позицію");
    }
    System.out.println(matcher.replaceFirst("Іра"));
    System.out.println(matcher.replaceAll("Ольга"));
    System.out.println(text);
}
Висновок програми: Знайдено збіг Алла з 5 по 8 позицію Знайдено збіг Анна зreplaceFirst 10 по replaceAll13 Stringпозицію шаблоном замінено на текст, переданий методом як аргумент. Причому метод replaceFirstзамінює лише перший збіг, а replaceAll– всі збіги у тесті. Початковий текст залишається без змін. Використання інших методів класу Matcher, а також приклади регулярних виразів можна переглянути в цьому циклі статей . Найбільш часті операції з регулярними виразами при роботі з текстом із класів Patternта Matcherвбудовані в класString. Це такі методи як split, matches, replaceFirst, replaceAll. Але насправді «під капотом» вони використовують класи Patternта Matcher. Тому, якщо потрібно замінити текст або порівняти рядки в програмі без написання зайвого коду, використовуйте методи класу String. Якщо вам потрібні розширені можливості – згадайте про класи Patternі Matcher.

Висновок

Регулярний вираз описується в Java-програмі за допомогою рядків, які підходять під визначений правилами шаблон. При виконанні коду Java перекомпілює цей рядок на об'єкт клас Patternі використовує об'єкт класу Matcherдля пошуку відповідності в тексті. Як я вже казав на початку, регулярні висловлювання дуже часто відкладають на потім, вважаючи складною темою. Однак якщо розібратися з основами синтаксису, метасимволами, екрануванням та вивчити приклади регулярних виразів, вони виявляються набагато простішими, ніж здаються на перший погляд.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ