Перейти к основному содержимому

Руководство по экранированию символов в Java RegExps

· 6 мин. чтения

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

1. Обзор

API регулярных выражений в Java, java.util.regex , широко используется для сопоставления с образцом. Чтобы узнать больше, вы можете следовать этой статье .

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

2. Специальные символы регулярного выражения

Согласно документации API регулярных выражений Java, в регулярном выражении присутствует набор специальных символов, также известных как метасимволы.

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

Метасимволы, которые нам обычно нужно экранировать таким образом:

<([{\^-=$!|]})?*+.>

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

Этот тест показывает, что для заданной входной строки foof шаблон foo . ( foo , оканчивающийся символом точки) соответствует, он возвращает значение true , что указывает на успешное совпадение.

@Test
public void givenRegexWithDot_whenMatchingStr_thenMatches() {
String strInput = "foof";
String strRegex = "foo.";

assertEquals(true, strInput.matches(strRegex));
}

Вы можете задаться вопросом, почему совпадение успешно, если во входной строке нет символа точки (.)?

Ответ прост. Точка (.) — это метасимвол. Особое значение точки здесь в том, что на ее месте может быть «любой символ». Поэтому понятно, как сопоставитель определил, что совпадение найдено.

Допустим, мы не хотим обрабатывать символ точки (.) с его уникальным значением. Вместо этого мы хотим, чтобы он интерпретировался как знак точки. Это означает, что в предыдущем примере мы не хотим, чтобы шаблон foo. чтобы иметь совпадение во входной строке.

Как бы мы поступили в подобной ситуации? Ответ таков: нам нужно экранировать символ точки (.), чтобы его особое значение игнорировалось.

Давайте углубимся в это более подробно в следующем разделе.

3. Экранирование символов

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

Давайте посмотрим, что они из себя представляют:

  1. Перед метасимволом ставится обратная косая черта ()
  2. Заключить метасимвол с \Q и \E

Это просто означает, что в примере, который мы видели ранее, если мы хотим избежать символа точки, нам нужно поместить символ обратной косой черты перед символом точки. В качестве альтернативы мы можем поместить символ точки между \Q и \E.

3.1. Экранирование с помощью обратной косой черты

Это один из методов, который мы можем использовать для экранирования метасимволов в регулярном выражении. Однако мы знаем, что символ обратной косой черты также является escape-символом в литералах Java String . Поэтому нам нужно удвоить символ обратной косой черты, когда он используется перед любым символом (включая сам символ ).

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

@Test
public void givenRegexWithDotEsc_whenMatchingStr_thenNotMatching() {
String strInput = "foof";
String strRegex = "foo\\.";

assertEquals(false, strInput.matches(strRegex));
}

Здесь символ точки экранирован, поэтому сопоставитель просто обрабатывает его как точку и пытается найти шаблон, оканчивающийся точкой (например, foo. ).

В этом случае он возвращает false , так как во входной строке нет совпадения для этого шаблона.

3.2. Экранирование с помощью \Q & \E

В качестве альтернативы мы можем использовать \Q и \E , чтобы экранировать специальный символ. \Q указывает, что все символы до \E должны быть экранированы, а \E означает, что нам нужно завершить экранирование, начатое с \Q .

Это просто означает, что все, что находится между \Q и \E , будет экранировано.

В показанном здесь тесте функция split() класса String выполняет сопоставление, используя предоставленное ему регулярное выражение.

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

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

Здесь экранирование выполняется путем помещения символа вертикальной черты между \Q и \E :

@Test
public void givenRegexWithPipeEscaped_whenSplitStr_thenSplits() {
String strInput = "foo|bar|hello|world";
String strRegex = "\\Q|\\E";

assertEquals(4, strInput.split(strRegex).length);
}

4. Метод Pattern.quote(String S)

Метод Pattern.Quote(String S) в классе java.util.regex.Pattern преобразует заданную строку шаблона регулярного выражения в буквальную строку шаблона. Это означает, что все метасимволы во входной строке рассматриваются как обычные символы.

Использование этого метода было бы более удобной альтернативой, чем использование \Q & \E , так как он оборачивает в них заданную строку .

Давайте посмотрим на этот метод в действии:

@Test
public void givenRegexWithPipeEscQuoteMeth_whenSplitStr_thenSplits() {
String strInput = "foo|bar|hello|world";
String strRegex = "|";

assertEquals(4,strInput.split(Pattern.quote(strRegex)).length);
}

В этом быстром тесте метод Pattern.quote() используется для выхода из заданного шаблона регулярного выражения и преобразования его в строковый литерал. Другими словами, он избегает для нас всех метасимволов, присутствующих в шаблоне регулярного выражения. Он выполняет ту же работу, что и \Q & \E .

Символ вертикальной черты экранируется методом Pattern.quote() , и функция split() интерпретирует его как строковый литерал, на который он делит ввод.

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

Следует отметить, что Pattern.quote заключает весь блок в одну escape-последовательность. Если бы мы хотели экранировать символы по отдельности, нам пришлось бы использовать алгоритм замены токенов .

5. Дополнительные примеры

Давайте посмотрим, как работает метод replaceAll() в java.util.regex.Matcher .

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

Представьте, что у нас есть ввод с несколькими вхождениями символа $ . В результате мы хотим получить ту же строку с заменой символа $ на £.

Этот тест демонстрирует, как шаблон $ передается без экранирования:

@Test
public void givenRegexWithDollar_whenReplacing_thenNotReplace() {

String strInput = "I gave $50 to my brother."
+ "He bought candy for $35. Now he has $15 left.";
String strRegex = "$";
String strReplacement = "£";
String output = "I gave £50 to my brother."
+ "He bought candy for £35. Now he has £15 left.";

Pattern p = Pattern.compile(strRegex);
Matcher m = p.matcher(strInput);

assertThat(output, not(equalTo(m.replaceAll(strReplacement))));
}

Тест утверждает, что $ неправильно заменен на £ .

Теперь, если мы избегаем шаблона регулярного выражения, замена происходит правильно, и тест проходит, как показано в этом фрагменте кода:

@Test
public void givenRegexWithDollarEsc_whenReplacing_thenReplace() {

String strInput = "I gave $50 to my brother."
+ "He bought candy for $35. Now he has $15 left.";
String strRegex = "\\$";
String strReplacement = "£";
String output = "I gave £50 to my brother."
+ "He bought candy for £35. Now he has £15 left.";
Pattern p = Pattern.compile(strRegex);
Matcher m = p.matcher(strInput);

assertEquals(output,m.replaceAll(strReplacement));
}

Обратите внимание на \\$ здесь, который выполняет трюк, экранируя символ $ и успешно сопоставляя шаблон.

6. Заключение

В этой статье мы рассмотрели экранирование символов в регулярных выражениях в Java.

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

Как всегда, исходный код, относящийся к этой статье, можно найти на GitHub .