Вступление
Что Вы знаете о обработке строк в Java? Как много этих знаний и насколько они углублены и актуальны? Давайте попробуем вместе со мной разобрать все вопросы, связанные с этой важной, фундаментальной и часто используемой частью языка. Наш маленький гайд будет разбит на пять публикаций, а именно:
- String, StringBuffer, StringBuilder (реализация строк)
- Pattern, Matcher (регулярные выражения)
- i18n (интернационализация)
- Кодировка символов (Unicode, UTF-8)
- Locale, ResourceBundle (локализация)
Сегодня поговорим о регулярных выражениях в Java, рассмотрим их механизм и подход к обработке. Также рассмотрим функциональные возможности пакета java.util.regex.
Регулярные выражения
Регулярные выражения (regular expressions, далее РВ) — мощное и эффективное средство для обработки текста. Они впервые были использованы в текстовых редакторах операционной системы UNIX (ed и QED) и сделали прорыв в электронной обработке текстов конца XX века. В 1987 году более сложные РВ возникли в первой версии языка Perl и были основаны на пакете Henry Spencer (1986), написанном на языке С. А в 1997 году, Philip Hazel разработал Perl Compatible Regular Expressions (PCRE) — библиотеку, что точно наследует функциональность РВ в Perl. Сейчас PCRE используется многими современными инструментами, например Apache HTTP Server.
![](https://habrastorage.org/files/fd1/350/d27/fd1350d27bb84df199ac607154b0c835.png)
Большинство современных языков программирования поддерживают РВ, Java не является исключением.
Механизм
Существует две базовые технологии, на основе которых строятся механизмы РВ:
- Недетерминированный конечный автомат (НКА) — «механизм, управляемый регулярным выражением»
- Детерминированный конечный автомат (ДКА) — «механизм, управляемый текстом»
НКА — механизм, в котором управление внутри РВ передается от компонента к компоненту. НКА просматривает РВ по одному компоненту и проверяет, совпадает ли компонент с текстом. Если совпадает — проверятся следующий компонент. Процедура повторяется до тех пор, пока не будет найдено совпадение для всех компонентов РВ (пока не получим общее совпадение).
ДКА — механизм, который анализирует строку и следит за всеми «возможными совпадениями». Его работа зависит от каждого просканированного символа текста (то есть ДКА «управляется текстом»). Даний механизм сканирует символ текста, обновляет «потенциальное совпадение» и резервирует его. Если следующий символ аннулирует «потенциальное совпадение», то ДКА возвращается к резерву. Нет резерва — нет совпадений.
Логично, что ДКА должен работать быстрее чем НКА (ДКА проверяет каждый символ текста не более одного раза, НКА — сколько угодно раз пока не закончит разбор РВ). Но НКА предоставляет возможность определять ход дальнейших событий. Мы можем в значительной степени управлять процессом за счет правильного написания РВ.
Регулярные выражения в Java используют механизм НКА.
Эти виды конечных автоматов более детально рассмотрены в статье «Регулярные выражения изнутри».
Подход к обработке
В языках программирования существует три подхода к обработке РВ:
- интегрированный
- процедурный
- объектно-ориентированный
Интегрированный подход — встраивание РВ в низкоуровневый синтаксис языка. Этот подход скрывает всю механику, настройку и, как следствие, упрощает работу программиста.
Функциональность РВ при процедурном и объектно-ориентированном подходе обеспечивают функции и методы соответственно. Вместо специальных конструкций языка, функции и методы принимают в качестве параметров строки и интерпретируют их как РВ.
Для обработки регулярных выражений в Java используют объектно-ориентированный подход.
Реализация
Для работы с регулярными выражениями в Java представлен пакет java.util.regex. Пакет был добавлен в версии 1.4 и уже тогда содержал мощный и современный прикладной интерфейс для работы с регулярными выражениями. Обеспечивает хорошую гибкость из-за использования объектов, реализующих интерефейс CharSequence.
Все функциональные возможности представлены двумя классами, интерфейсом и исключением:
![](https://hsto.org/files/e19/247/5ac/e192475acc6146099023f6b18a6c3873.png)
Pattern
Класс Pattern представляет собой скомпилированное представление РВ. Класс не имеет публичных конструкторов, поэтому для создания объекта данного класса необходимо вызвать статический метод compile и передать в качестве первого аргумента строку с РВ:
// XML тэг в формате <xxx></xxx> Pattern pattern = Pattern.compile("^<([a-z]+)([^>]+)*(?:>(.*)<\\/\\1>|\\s+\\/>)$");
Также в качестве второго параметра в метод compile можно передать флаг в виде статической константы класса Pattern, например:
// email адрес в формате xxx@xxx.xxx (регистр букв игнорируется) Pattern pattern = Pattern.compile("^([a-z0-9_\\.-]+)@([a-z0-9_\\.-]+)\\.([a-z\\.]{2,6})$", Pattern.CASE_INSENSITIVE);
Таблица всех доступных констант и эквивалентных им флагов:
№ | Constant | Equivalent Embedded Flag Expression |
---|---|---|
1 | Pattern.CANON_EQ | — |
2 | Pattern.CASE_INSENSITIVE | (?i) |
3 | Pattern.COMMENTS | (?x) |
4 | Pattern.MULTILINE | (?m) |
5 | Pattern.DOTALL | (?s) |
6 | Pattern.LITERAL | — |
7 | Pattern.UNICODE_CASE | (?u) |
8 | Pattern.UNIX_LINES | (?d) |
Иногда нам необходимо просто проверить есть ли в строке подстрока, что удовлетворяет заданному РВ. Для этого используют статический метод matches, например:
// это hex код цвета? if (Pattern.matches("^#?([a-f0-9]{6}|[a-f0-9]{3})$", "#8b2323")) { // вернет true // делаем что-то }
Также иногда возникает необходимость разбить строку на массив подстрок используя РВ. В этом нам поможет метод split:
Pattern pattern = Pattern.compile(":|;"); String[] animals = pattern.split("cat:dog;bird:cow"); Arrays.asList(animals).forEach(animal -> System.out.print(animal + " ")); // cat dog bird cow
Matcher и MatchResult
Matcher — класс, который представляет строку, реализует механизм согласования (matching) с РВ и хранит результаты этого согласования (используя реализацию методов интерфейса MatchResult). Не имеет публичных конструкторов, поэтому для создания объекта этого класса нужно использовать метод matcher класса Pattern:
// будем искать URL String regexp = "^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$"; String url = "http://habrahabr.ru/post/260767/"; Pattern pattern = Pattern.compile(regexp); Matcher matcher = pattern.matcher(url);
Но результатов у нас еще нет. Чтобы их получить нужно воспользоваться методом find. Можно использовать matches — этот метод вернет true только тогда, когда вся строка соответствует заданному РВ, в отличии от find, который пытается найти подстроку, которая удовлетворяет РВ. Для более детальной информации о результатах согласования можно использовать реализацию методов интерфейса MatchResult, например:
// IP адрес String regexp = "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"; // для сравнения работы find() и matches() String goodIp = "192.168.0.3"; String badIp = "192.168.0.3g"; Pattern pattern = Pattern.compile(regexp); Matcher matcher = pattern.matcher(goodIp); // matches() - true, find() - true matcher = pattern.matcher(badIp); // matches() - false, find() - true // а теперь получим дополнительную информацию System.out.println(matcher.find() ? "I found '"+matcher.group()+"' starting at index "+matcher.start()+" and ending at index "+matcher.end()+"." : "I found nothing!"); // I found the text '192.168.0.3' starting at index 0 and ending at index 11.
Также можно начинать поиск с нужной позиции используя find(int start). Стоит отметить что существует еще один способ поиска — метод lookingAt. Он начинает проверку совпадений РВ с начала строки, но не требует полного соответствия, в отличии от matches.
Класс предоставляет методы для замены текста в указанной строке:
appendReplacement(StringBuffer sb, String replacement) | Реализует механизм «добавление-и-замена» (append-and-replace). Формирует обьект StringBuffer (получен как параметр) добавляя replacement в нужные места. Устанавливает позицию, которая соответствует end() последнего результата поиска. После этой позиции ничего не добавляет. |
appendTail(StringBuffer sb) | Используется после одного или нескольких вызовов appendReplacement и служит для добавления оставшейся части строки в объект класса StringBuffer, полученного как параметр. |
replaceFirst(String replacement) | Заменяет первую последовательность, которая соответствует РВ, на replacement. Использует вызовы методов appendReplacement и appendTail. |
replaceAll(String replacement) | Заменяет каждую последовательность, которая соответствует РВ, на replacement. Также использует методы appendReplacement и appendTail. |
quoteReplacement(String s) | Возвращает строку, в которой коса черта (‘ \ ‘) и знак доллара (‘ $ ‘) будут лишены особого смысла. |
Pattern pattern = Pattern.compile("a*b"); Matcher matcher = pattern.matcher("aabtextaabtextabtextb the end"); StringBuffer buffer = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(buffer, "-"); // buffer = "-" -> "-text-" -> "-text-text-" -> "-text-text-text-" } matcher.appendTail(buffer); // buffer = "-text-text-text- the end"
PatternSyntaxException
Неконтролируемое (unchecked) исключение, возникает при синтаксической ошибке в регулярном выражении. В таблице ниже приведены все методы и их описание.
getDescription() | Возвращает описание ошибки. |
getIndex() | Возвращает индекс строки, где была найдена ошибка в РВ |
getPattern() | Возвращает ошибочное РВ. |
getMessage() | getDescription() + getIndex() + getPattern() |
Спасибо за внимание. Все дополнения, уточнения и критика приветствуются.
ссылка на оригинал статьи http://habrahabr.ru/post/260773/
Добавить комментарий