Предлагаем вашему вниманию перевод краткого руководства по регулярным выражениям в языке Java, написанного Джеффом Фрисеном (Jeff Friesen) для сайта
javaworld. Для простоты чтения мы разделили статью на несколько частей.
Регулярные выражения в 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 различает следующие разделители строк:
- cимвол возврата каретки (
\r );
- cимвол новой строки (символ протяжки бумаги на одну строку) (
\n );
- cимвол возврата каретки, за которым непосредственно следует символ новой строки (
\r\n );
- cимвол следующей строки (
\u0085 );
- cимвол разделения строк (
\u2028 );
- cимвол разделения абзацев (
\u2029 )
|
Захватываемые группы
Захватываемая группа (capturing group) служит для сохранения найденного набора символов с целью дальнейшего использования при поиске по шаблону. Эта конструкция представляет собой последовательность символов, заключенную в метасимволы круглых скобок (
( )
). Все символы внутри захватываемой группы рассматриваются при поиске по шаблону как единое целое. Например, захватываемая группа (
Java
) объединяет буквы
J
,
a
,
v
и
a
в единое целое. Эта захватываемая группа находит все вхождения шаблона
Java
во входной текст. При каждом совпадении предыдущие сохраненные символы
Java
заменяются следующими.
Захватываемые группы могут быть вложены в другие захватываемые группы. Например, в регулярном выражении
(Java( language))
группа
(language)
вложена внутрь группы
(Java)
. Каждой вложенной или не вложенной захватываемой группе присваивается свой номер, начиная с 1, причем нумерация идет слева направо. В предыдущем примере,
(Java( language))
соответствует захватываемой группе номер 1, а
(language)
– захватываемой группе номер 2. В регулярном выражении
(a)(b)
,
(a)
соответствует захватываемой группе номер 1, а
(b)
захватываемой группе номер 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
.
Квантификаторы
Квантификатор – это конструкция регулярного выражения, явно или неявно связывающая шаблон с числовым значением. Этот числовое значение определяет, сколько раз искать шаблон. Квантификаторы делятся на жадные, ленивые и сверхжадные:
- Жадный квантификатор (
?
, *
или +
) предназначен для поиска самого длинного совпадения. Можно задать 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
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ