JavaRush /Java блог /Random UA /Регулярні вирази в Java, частина 2
Professor Hans Noodles
41 рівень

Регулярні вирази в Java, частина 2

Стаття з групи Random UA
Пропонуємо до вашої уваги переклад короткого посібника з регулярних виразів у мові Java, написаного Джеффом Фрісеном (Jeff Friesen) для сайту javaworld . Для простоти читання ми розділабо статтю кілька частин. Регулярні вирази у Java, частина 2 - 1Регулярні вирази в Java, частина 1
Злиття кількох діапазонів
Можна зливати кілька діапазонів в один діапазонний клас символів, розміщуючи їх пліч-о-пліч. Наприклад, клас [a-zA-Z]відповідає всім латинським алфавітним символам у нижньому або верхньому регістрі.

Злиття кількох діапазонів

Можна зливати кілька діапазонів в один діапазонний клас символів шляхом розміщення їх пліч-о-пліч. Наприклад, клас [a-zA-Z]відповідає всім латинським алфавітним символам у нижньому або верхньому регістрі.

Об'єднання класів символів

Об'єднання класів символів складається з кількох вкладених класів символів і відповідає всім символам, що входять у результуюче об'єднання. Наприклад, клас [a-d[m-p]]відповідає символам від aдо dта від mдо p. Розглянемо наступний приклад: java RegexDemo [ab[c-e]] abcdef У цьому прикладі будуть знайдені символи a, b, c, dі eдля яких є відповідності в abcdef:
regex = [ab[c-e]]
input = abcdef
Found [a] starting at 0 and ending at 0
Found [b] starting at 1 and ending at 1
Found [c] starting at 2 and ending at 2
Found [d] starting at 3 and ending at 3
Found [e] starting at 4 and ending at 4

Перетин класів символів

Перетин класів символів складається з символів, загальних всім вкладених класів і відповідає лише загальним символам. Наприклад, клас [a-z&&[d-f]]відповідає символам d, eта f. Розглянемо наступний приклад: java RegexDemo "[aeiouy&&[y]]" party Зверніть увагу, що на моїй операційній системі Windows потрібні подвійні лапки, оскільки командна оболонка розглядає &як роздільник команд. У цьому прикладі буде знайдено лише символ y, для якого є відповідність до party:
regex = [aeiouy&&[y]]
input = party
Found [y] starting at 4 and ending at 4

Віднімання класів символів

Віднімання класів символів складається з усіх символів, крім тих, які містяться у вкладених класах символів, і відповідає лише цим символам. Наприклад, клас [a-z&&[^m-p]]відповідає символам від aдо lі від qдо z: java RegexDemo "[a-f&&[^a-c]&&[^e]]" abcdefg У цьому прикладі буде знайдено символи dі fдля яких є відповідності в abcdefg:
regex = [a-f&&[^a-c]&&[^e]]
input = abcdefg
Found [d] starting at 3 and ending at 3
Found [f] starting at 5 and ending at 5

Зумовлені класи символів

Деякі класи символів зустрічаються у регулярних виразах досить часто, щоб виправдати використання скорочених позначень. Клас Patternпропонує як подібні скорочення визначені класи символів. Ви можете скористатися ними, щоб спростити свої регулярні вирази та мінімізувати синтаксичні помилки. Є кілька категорій визначених класів символів: стандартні, POSIX, java.lang.Characterта такі властивості Unicode, як сценарій, блок, категорія та двійкова властивість. У наступному списку наведено лише категорію стандартних класів:
  • \d: Цифра. Еквівалентно [0-9].
  • \D: Нецифровий символ Еквівалентно [^0-9].
  • \s: символ пробілу. Еквівалентно [ \t\n\x0B\f\r].
  • \S: Не символ пробілу. Еквівалентно [^\s].
  • \w: Словотворчий символ Еквівалентно [a-zA-Z_0-9].
  • \W: Не словотворний символ Еквівалентно [^\w].
У наступному прикладі використовується визначений клас символів \wдля опису всіх словотворчих символів у вхідному тексті: java RegexDemo \w "aZ.8 _" Уважно подивіться на наступні результати виконання, які показують, що символи точки і пробілу не вважаються словотворчим:
regex = \w
input = aZ.8 _
Found [a] starting at 0 and ending at 0
Found [Z] starting at 1 and ending at 1
Found [8] starting at 3 and ending at 3
Found [_] starting at 5 and ending at 5
Розділювачі рядків
Документація по SDK класу Patternописує метасимвол точки як зумовлений клас символів, що відповідає будь-якому символу, крім роздільників рядків (одно-або двосимвольні послідовності, що відзначають кінець рядка). Виняток становить режим dotall (який ми обговоримо далі), у якому точка відповідають роздільникам рядків. Клас Patternрозрізняє наступні роздільники рядків:
  • символ повернення каретки ( \r);
  • символ нового рядка (символ протягування паперу на один рядок) ( \n);
  • символ повернення каретки, за яким безпосередньо слідує символ нового рядка ( \r\n);
  • символ наступного рядка ( \u0085);
  • символ поділу рядків ( \u2028);
  • символ розділення абзаців ( \u2029)

Захоплювані групи

Захоплювана група (capturing group) служить збереження знайденого набору символів з подальшого використання під час пошуку за шаблоном. Ця конструкція є послідовністю символів, укладеною в метасимволи круглих дужок ( ( )). Всі символи всередині групи, що захоплюється, розглядаються при пошуку за шаблоном як єдине ціле. Наприклад, захоплююча група ( Java) поєднує літери J, a, vі aєдине ціле. Ця захоплива група знаходить усі входження шаблону Javaу вхідний текст. При кожному збігу попередні збережені символи Javaзамінюються на наступні. Захоплювані групи можуть бути вкладені в інші групи, що захоплюються. Наприклад, у регулярному вираженні (Java( language))група (language)вкладена всередину групи(Java). Кожній вкладеній або не вкладеній групі, що захоплюється, присвоюється свій номер, починаючи з 1, причому нумерація йде зліва направо. У попередньому прикладі (Java( language))відповідає групі номер 1, а (language)– групі номер 2. У регулярному виразі відповідає групі номер 1, а групі номер 2 (a)(b).(a)(b)Регулярні вирази в Java, частина 2 - 2До збережених захоплюваних груп збігів можна пізніше звернутися за допомогою зворотних посилань. Зворотне посилання, що задається у вигляді символу зворотної косої риси з наступним цифровим символом, що відповідає номеру захоплюючої групи, зворотне посилання дозволяє звернутися до символів захопленого групою тексту. Наявність зворотного посилання призводить до звернення зіставника до збереженого захоплюваною групою результату пошуку, на основі номера з неї, з подальшим використанням символів цього результату для спроби подальшого пошуку. У цьому прикладі показано використання зворотного посилання для пошуку граматичних помилок у тексті: java RegexDemo "(Java( language)\2)" "The Java language language" У цьому прикладі регулярне вираження (Java( language)\2)використовується для пошуку граматичної помилки з дублюванням слова languageбезпосередньо після Javaу вхідному тексті"The Java language language". У цьому регулярному виразі задані дві захоплюючі групи: номер 1 – (Java( language)\2), відповідної Java language languageта номер 2 – (language), що відповідає символу пропуску, за яким слідує language. Зворотне посилання \2дозволяє повторно звернутися до збереженого результату групи номер 2, завдяки чому співставник може виконати пошук другого входження пробілу, за яким слідує language, безпосередньо після першого входження пробілу та language. Результати роботи співставника RegexDemoє таким:
regex = (Java( language)\2)
input = The Java language language
Found [Java language language] starting at 4 and ending at 25

Граничні зіставники

Іноді буває потрібно виконати зіставлення з шаблоном на початку рядка, межі слів, наприкінці тексту тощо. Зробити це можна за допомогою одного з граничних зіставників класу Pattern, що являють собою конструкції регулярних виразів для пошуку збігів у наступних місцях:
  • ^: Початок рядка;
  • $: Кінець рядка;
  • \b: Кордон слова;
  • \B: Кордон псевдослова;
  • \A: Початок тексту;
  • \G: Кінець попереднього збігу;
  • \Z: Кінець тексту, крім підсумкового роздільника рядків (якщо є);
  • \z: Кінець тексту
У наступному прикладі використовується метасимвол ^граничного співставника для пошуку рядків, що починаються з The, за яким слідує нуль або більше словотворних символів: java RegexDemo "^The\w*" Therefore Символ ^вказує на те, що перші три символи вхідного тексту повинні відповідати символам шаблону , що йдуть один за одним T, hі e, за якими може слідувати будь-яке число словотворних символів. Ось результат виконання:
regex = ^The\w*
input = Therefore
Found [Therefore] starting at 0 and ending at 8
Що станеться, якщо змінити командний рядок на java RegexDemo "^The\w*" " Therefore"? Збігу знайдено не буде, оскільки перед Thereforeу вхідному тексті стоїть символ пропуску.

Збіги нульової довжини

Іноді при роботі з граничними співставниками ви зустрічатиметеся з збігами нульової довжини. Совпадение нулевой длины- Це збіг, що не містить символів. Вони можуть зустрітися в порожньому вхідному тексті, на початку вхідного тексту, після останнього символу вхідного тексту та між будь-якими двома символами цього тексту. Збіги нульової довжини легко розпізнати, оскільки вони завжди починаються і закінчуються на одній позиції. Розглянемо наступний приклад: java RegExDemo \b\b "Java is" У цьому прикладі відбувається пошук двох послідовних меж слова, його результати виглядають так:
regex = \b\b
input = Java is
Found [] starting at 0 and ending at -1
Found [] starting at 4 and ending at 3
Found [] starting at 5 and ending at 4
Found [] starting at 7 and ending at 6
Ми бачимо у результатах кілька збігів нульової довжини. Кінцеві позиції тут на одиницю менші за початкові, оскільки у вихідному коді RegexDemoв лістингу 1 я вказав end() – 1. Регулярні вирази у Java, частина 2 - 3

Квантифікатори

Квантифікатор - це конструкція регулярного виразу, що явно або неявно пов'язує шаблон з числовим значенням. Це числове значення визначає, скільки разів шукати шаблон. Квантифікатори поділяються на жадібні, ліниві та над жадібні:
  • Жадібний квантифікатор ( ?, *або +) призначений для пошуку найдовшого збігу. Можна задати X? для пошуку одного або менших входжень X, X*для пошуку нуля або більше входжень X, X+для пошуку одного або більше входжень X, X{n}для пошуку nвходжень X, X{n,}для пошуку не менше (а можливо, і більше) nвходжень Xі X{n,m}для пошуку не менше n, але не більше mвходжень X.
  • Лінивий квантифікатор ( ??, *?або +?) призначений для пошуку найкоротшого збігу. Чи можна задати X??для пошуку одного або менш входжень X, X*? для пошуку нуля або більше входжень X, X+?для пошуку одного або більше входжень X, X{n}?для пошуку nвходження X, X{n,}?для пошуку не менше (а можливо, і більше) nвходжень Xі X{n,m}?для пошуку не менше n, але не більше mвходжень X.
  • Над жадібний квантифікатор ( ?+, *+або ++) аналогічний жадібному, за винятком того, що наджадібний квантифікатор виконує тільки одну спробу знайти найдовший збіг, в той час як жадібний може виконувати кілька спроб. Можна задати X?+для пошуку одного або менш входжень X, X*+для пошуку нуля або більше входжень X, X++для пошуку одного або більше входжень X, X{n}+для пошуку nвходжень X, X{n,}+для пошуку не менше (а можливо, і більше) nвходжень Xі X{n,m}+ для пошуку не менше n, але не більше mвходжень X.
Наступний приклад ілюструє використання жадібного квантифікатора: java RegexDemo .*ox "fox box pox" Ось його результати:
regex = .*ox
input = fox box pox
Found [fox box pox] starting at 0 and ending at 10
Жадібний квантифікатор ( .*) знаходить найдовшу послідовність символів, що завершується на ox. Він поглинає весь вхідний текст, після чого відкочується до виявлення того, що вхідний текст закінчується цими символами. Розглянемо тепер лінивий квантифікатор: java RegexDemo .*?ox "fox box pox" Його результати:
regex = .*?ox
input = fox box pox
Found [fox] starting at 0 and ending at 2
Found [ box] starting at 3 and ending at 6
Found [ pox] starting at 7 and ending at 10
Лінивий квантифікатор ( .*?) знаходить найкоротшу послідовність символів, що завершується на ox. Він починає з порожнього рядка і поступово поглинає символи доти, доки знаходить відповідність. А потім продовжує роботу до вичерпання вхідного тексту. Нарешті, розглянемо надмірний квантифікатор: java RegexDemo .*+ox "fox box pox" І ось його результати:
regex = .*+ox
input = fox box pox
Надто жадібний квантифікатор ( .*+) не знаходить збігів, оскільки він поглинає весь вхідний текст і не залишається нічого, що могло б відповідати oxв кінці регулярного виразу. На відміну від жадібного квантифікатора, наджадібний квантифікатор не виконує відкат назад.

Збіги нульової довжини

Іноді при роботі з квантифікаторами ви стикатиметеся з збігами нульової довжини. Наприклад, використання наступного жадібного квантифікатора призводить до кількох збігів нульової довжини: java RegexDemo a? abaa Результати виконання цього прикладу:
regex = a?
input = abaa
Found [a] starting at 0 and ending at 0
Found [] starting at 1 and ending at 0
Found [a] starting at 2 and ending at 2
Found [a] starting at 3 and ending at 3
Found [] starting at 4 and ending at 3
У результатах виконання є п'ять збігів. Хоча перше, третє і четверте цілком очікувані (вони відповідають позиціям трьох літер aу abaa), друге та п'яте може вас здивувати. Створюється таке враження, ніби вони вказують, що aвідповідає bй кінцю тексту, але насправді не так. Регулярний вираз a?не шукає bнаприкінці тексту. Воно виконує пошук наявності чи відсутності a. Коли a?не виявляє a, повідомляє про це у вигляді збігу нульової довжини.

Вкладені прапори

Зіставники приймають деякі припущення за умовчанням, які можна перекрити при компіляції регулярного виразу шаблону. Ми обговоримо це питання згодом. Регулярний вираз дозволяє перекрити будь-яке із умовчань, використовуючи вкладений прапор. Ця конструкція регулярного виразу задається у вигляді метасимволів круглих дужок навколо метасимволу знака питання ( ?), з наступною латинською літерою в нижньому регістрі. Клас Patternрозуміє такі вкладені прапорові вирази:
  • (?i): активує нечутливий до регістру пошук шаблону. Наприклад, під час використання команди java RegexDemo (?i)tree Treehouseпослідовність символів Treeвідповідає шаблону tree. За промовчанням використовується пошук за шаблоном з урахуванням регістру.
  • (?x): дозволяє використовувати всередині шаблону пробілові символи та коментарі, що починаються з метасимволу #. Зіставник ігноруватиме і ті, й інші. Наприклад, java RegexDemo ".at(?x)#match hat, cat, and so on" matterпослідовність символів matвідповідає шаблону .at. За промовчанням пробілові символи та коментарі заборонені, співставник розглядає їх як символи, що беруть участь у пошуку.
  • (?s): активує режим dotall, у якому метасимвол точки відповідає роздільникам рядків, крім іншого символу. Наприклад, при команді java RegexDemo (?s). \nбуде знайдено символ нового рядка. За замовчуванням використовується протилежний режим dotall: роздільники рядків не будуть перебувати. Наприклад, при команді Java RegexDemo . \nсимвол нового рядка не знайдено.
  • (?m): активує багаторядковий режим, у якому ^відповідає початку, а $– кінця кожного рядка. Наприклад, java RegexDemo "(?m)^abc$" abc\nabcзнаходить у вхідному тексті обидві послідовності abc. За замовчуванням використовується однорядковий режим: ^відповідає початку всього вхідного тексту, а $його кінця. Наприклад, java RegexDemo "^abc$" abc\nabcповертає відповідь про відсутність збігів.
  • (?u): активує вирівнювання регістру з урахуванням Unicode. Цей прапор, при спільному використанні з (?i)дозволяє здійснювати пошук за шаблоном без урахування регістру відповідно до стандарту Unicode. Налаштування за замовчуванням — пошук з урахуванням регістру і лише за символами US-ASCII.
  • (?d): активує режим рядків у стилі Unix, у якому зіставник розпізнає у тих метасимволів ., ^і $лише роздільник рядків \n. За замовчуванням використовується режим рядків у стилі не-Unix: зіставник розпізнає у контексті вищезазначених метасимволів усі роздільники рядків.
Вкладені прапорові вирази нагадують групи, що захоплюються, оскільки їх символи оточуються метасимволами круглих дужок. На відміну від груп, що захоплюються, вкладені прапорові вирази – приклад не захоплюваних груп, що є конструкцією регулярних виразів, що не захоплює текстові символи. Вони визначаються як послідовності символів, оточені метасимволами круглих дужок.
Вказівка ​​кількох вкладених прапорових виразів
Існує можливість вказівки кількох вкладених прапорових виразів у регулярному виразі шляхом або розташування їх пліч-о-пліч ( (?m)(?i))), або розміщення послідовно визначальних їх літер ( (?mi)).

Висновок

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