认真来看下正则表达式

发布于:2024-09-05 ⋅ 阅读:(69) ⋅ 点赞:(0)

目录

一、简单谈谈正则

二、基础知识学习

(一)正则元字符

1.特殊单字符

2.空白符

3.量词

4.范围备和选项

综合练习

(二)贪婪、非贪婪与独占模式

1.贪婪模式

2.非贪婪模式(懒惰模式)

3.独占模式

(三)分组与引用

1.捕获分组(Capturing Group)

2.非捕获分组(Non-Capturing Group)

3.命名捕获分组(Named Capturing Group)

4. 引用(Backreference)

5. 重复分组

(四)匹配模式

1.不区分大小写

2.点号通配模式

3.多行模式

4.注释模式

(五)断言(Assertion)

1.单词的边界

2.行的开始或结束

3.环视(Lookaround)

(六)正则转义


干货分享,感谢您的阅读!

一、简单谈谈正则

正则,也称为正则表达式(Regular Expression),是一种用于文本匹配和搜索的强大工具。它是一种由字符和特殊符号组成的字符串模式,用于描述和匹配一组文本字符串,而不是固定的字符串。正则表达式可以做以下事情:

正则表达式的语法和规则因编程语言和库的不同而有所差异,但它们在处理文本数据时都具有广泛的应用。学习正则表达式可以提高文本处理和数据提取的效率,但也需要花一些时间来掌握它们的复杂性。不同编程语言和工具提供不同的正则表达式支持,所以你需要查阅相应的文档来学习如何在特定环境中使用正则表达式。

当前写的正则可以直接在线验证:正则表达式在线测试 | 菜鸟工具

二、基础知识学习

(一)正则元字符

1.特殊单字符

当描述正则表达式中的特殊单字符元字符时,可以整合为一个表格,如下所示:

元字符 描述 示例
. 匹配除换行符 \n 之外的任何单个字符。 "c.t" 可以匹配 "cat"、"cut"、"c@t" 等
\d 匹配任何单个数字(0-9)。 "The answer is 42" 中的 \d 可以匹配 "4" 和 "2"
\w 匹配任何单个字母、数字或下划线字符(单词字符)。 "word_123" 中的 \w 可以匹配 "w"、"o"、"r"、"d"、"1"、"2"、"3" 和 "_"
\s 匹配任何单个空白字符,包括空格、制表符、换行符等。 "Hello\tworld\n" 中的 \s 可以匹配制表符和换行符
\D 匹配任何非数字字符。 "Hello, World!" 中的 \D 可以匹配 ","、"H"、"e"、"l"、"l"、"o"、" "、"W" 等
\W 匹配任何非单词字符。 "text-with-hyphen" 中的 \W 可以匹配 "-"
\S 匹配任何非空白字符。 "This is text" 中的 \S 可以匹配 "T"、"h"、"i"、"s"、"i"、"s"、"t"、"e"、"x"、"t"

这个表格提供了有关这些特殊单字符元字符的简明描述以及示例用法,有助于理解它们的用途和功能。在实际使用正则表达式时,可以根据需要结合这些元字符来构建匹配模式。

我们使用java验证以上内容如下:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 特殊单字符元字符验证
 * @author: zhangyanfeng
 * @create: 2023-11-05 00:04
 **/
public class SpecialSingleCharRegexExample {
    public static void main(String[] args) {
        // 使用正则表达式验证特殊单字符元字符

        // 创建匹配器
        MatcherWithInfo("1. Matches for 'c.t'", "cat cut c@t", "c.t");
        MatcherWithInfo("2. Matches for '\\d'", "The answer is 42", "\\d");
        MatcherWithInfo("3. Matches for '\\w'", "word_123", "\\w");
        MatcherWithInfo("4. Matches for '\\s'", "Hello\tworld\n", "\\s");

        // 反义元字符

        MatcherWithInfo("5. Matches for '\\D'", "Hello, World!", "\\D");
        MatcherWithInfo("6. Matches for '\\W'", "text-with-hyphen", "\\W");
        MatcherWithInfo("7. Matches for '\\S'", "This is text", "\\S");
    }

    // 匹配器并打印结果
    private static void MatcherWithInfo(String header, String text, String regex) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        StringBuilder matches = new StringBuilder();

        while (matcher.find()) {
            matches.append(matcher.group()).append(" ");
        }

        System.out.println(header);
        System.out.println("   Original Text: " + text);
        System.out.println("   Matching Result: " + (matches.length() > 0 ? matches.toString().trim() : "No Match"));
        System.out.println();
    }
}

验证结果如下:

1. Matches for 'c.t'
   Original Text: cat cut c@t
   Matching Result: cat cut c@t

2. Matches for '\d'
   Original Text: The answer is 42
   Matching Result: 4 2

3. Matches for '\w'
   Original Text: word_123
   Matching Result: w o r d _ 1 2 3

4. Matches for '\s'
   Original Text: Hello	world

   Matching Result: 

5. Matches for '\D'
   Original Text: Hello, World!
   Matching Result: H e l l o ,   W o r l d !

6. Matches for '\W'
   Original Text: text-with-hyphen
   Matching Result: - -

7. Matches for '\S'
   Original Text: This is text
   Matching Result: T h i s i s t e x t

2.空白符

在正则表达式中,空白符通常表示为转义序列,用于匹配文本中的空格、制表符、换行符等空白字符。以下是一些常见的空白符的表示方式和它们的含义:

元字符 描述
\s 匹配任何单个空白字符,包括空格、制表符、换行符、回车符和换页符等。
\t 匹配制表符(Tab)字符。
\n 匹配换行符(newline),用于表示文本中的新行。
\r 匹配回车符(carriage return),通常用于控制光标的位置。
\f 匹配换页符(form feed),通常用于分页打印。
\v 匹配垂直制表符(vertical tab)。
\h 匹配水平空白符(horizontal whitespace),包括空格和制表符。
\v 匹配垂直空白符(vertical whitespace),包括换行符、回车符等。
\S 匹配任何单个非空白字符,与\s 的作用相反。

这些转义字符可以用于构建正则表达式模式,以匹配特定类型的空白符或在文本中查找空白字符。根据需求,可以选择使用这些转义字符来处理文本中的空白符。

我们使用java验证以上内容如下:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 验证和打印正则表达式空白符元字符
 * @author: zhangyanfeng
 * @create: 2023-11-05 12:21
 **/
public class WhitespaceRegexExample {
    public static void main(String[] args) {
        // 使用正则表达式验证空白符元字符

        // 创建匹配器并打印结果
        MatcherWithInfo("1. Matches for '\\s':", "This is some text with spaces and tabs.", "\\s");
        MatcherWithInfo("2. Matches for '\\t':", "Tab\tSeparated\tText", "\\t");
        MatcherWithInfo("3. Matches for '\\n':", "Newline\nSeparated\nText", "\\n");
        MatcherWithInfo("4. Matches for '\\r':", "Carriage\rReturn", "\\r");
        MatcherWithInfo("5. Matches for '\\f':", "Form\fFeed", "\\f");
        MatcherWithInfo("6. Matches for '\\v':", "Vertical\\vTab", "\\v");
        MatcherWithInfo("7. Matches for '\\h':", "Horizontal hWhitespace hExample", "\\h");
        MatcherWithInfo("8. Matches for '\\v':", "Vertical vWhitespace vExample", "\\v");
        MatcherWithInfo("9. Matches for '\\S':", "This_is_non-space_text", "\\S");
    }

    // 匹配器并打印结果
    private static void MatcherWithInfo(String header, String text, String regex) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        StringBuilder matches = new StringBuilder();

        while (matcher.find()) {
            matches.append(matcher.group()).append(" ");
        }

        System.out.println(header);
        System.out.println("   Original Text: " + text);
        System.out.println("   Matching Result: " + (matches.length() > 0 ? matches.toString().trim() : "No Match"));
        System.out.println();
    }
}

验证结果如下:

1. Matches for '\s':
   Original Text: This is some text with spaces and tabs.
   Matching Result: 

2. Matches for '\t':
   Original Text: Tab	Separated	Text
   Matching Result: 

3. Matches for '\n':
   Original Text: Newline
Separated
Text
   Matching Result: 

4. Matches for '\r':
Return
   Matching Result: 

5. Matches for '\f':
   Original Text: FormFeed
   Matching Result: 

6. Matches for '\v':
   Original Text: Vertical\vTab
   Matching Result: No Match

7. Matches for '\h':
   Original Text: Horizontal hWhitespace hExample
   Matching Result: 

8. Matches for '\v':
   Original Text: Vertical vWhitespace vExample
   Matching Result: No Match

9. Matches for '\S':
   Original Text: This_is_non-space_text
   Matching Result: T h i s _ i s _ n o n - s p a c e _ t e x t

3.量词

量词是正则表达式中的元字符,用于指定某个部分的重复次数。它们允许匹配单个字符或模式的重复出现,从零次到多次。以下是一些常见的量词元字符及其含义:

元字符 描述 示例
* 匹配前面的元素零次或多次。 a* 匹配 "a"、"aa"、"aaa" 等。
+ 匹配前面的元素至少一次或多次。 b+ 匹配 "b"、"bb"、"bbb" 等。
? 匹配前面的元素零次或一次。 c? 匹配 "c" 或空字符串。
{m} 匹配前面的元素精确 m 次。 d{3} 匹配 "ddd"。
{m,} 匹配前面的元素至少 m 次。 e{2,} 匹配 "ee"、"eee" 等。
{m,n} 匹配前面的元素 m 到 n 次,包括 m 和 n。 f{1,3} 匹配 "f"、"ff"、"fff"。

这些量词元字符使你能够更灵活地定义正则表达式,以匹配不同数量的字符或模式。它们对于匹配重复出现的文本模式非常有用,如匹配电话号码、日期、电子邮件地址等。

我们使用java验证以上内容如下:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 验证正则表达式量词元字符
 * @author: zhangyanfeng
 * @create: 2023-11-05 12:34
 **/
public class QuantifierRegexExample {
    public static void main(String[] args) {
        // 使用正则表达式验证量词元字符

        // 创建匹配器并打印结果
        MatcherWithInfo("1. Matches for 'a*':", "aaaabb", "a*");
        MatcherWithInfo("2. Matches for 'b+':", "aaaabb", "b+");
        MatcherWithInfo("3. Matches for 'c?':", "aaaabb", "c?");
        MatcherWithInfo("4. Matches for 'd{3}':", "aaadddbb", "d{3}");
        MatcherWithInfo("5. Matches for 'e{2,}':", "eeeefbb", "e{2,}");
        MatcherWithInfo("6. Matches for 'f{1,3}':", "ffbbfffb", "f{1,3}");
    }

    // 匹配器并打印结果
    private static void MatcherWithInfo(String header, String text, String regex) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        StringBuilder matches = new StringBuilder();

        while (matcher.find()) {
            matches.append(matcher.group()).append(" ");
        }

        System.out.println(header);
        System.out.println("   Original Text: " + text);
        System.out.println("   Matching Result: " + (matches.length() > 0 ? matches.toString().trim() : "No Match"));
        System.out.println();
    }
}

验证结果如下:

1. Matches for 'a*':
   Original Text: aaaabb
   Matching Result: aaaa

2. Matches for 'b+':
   Original Text: aaaabb
   Matching Result: bb

3. Matches for 'c?':
   Original Text: aaaabb
   Matching Result: 

4. Matches for 'd{3}':
   Original Text: aaadddbb
   Matching Result: ddd

5. Matches for 'e{2,}':
   Original Text: eeeefbb
   Matching Result: eeee

6. Matches for 'f{1,3}':
   Original Text: ffbbfffb
   Matching Result: ff fff

4.范围备和选项

范围元字符用于匹配特定范围内的字符或数字。它们允许你指定一个字符或数字必须在某个范围内才能匹配成功。以下是常见的范围元字符及其含义:

范围元字符 描述 示例
| 表示或的关系通常用于选择多个备选项之一 "apple|banana|cherry"将匹配文本中包含 "apple"、"banana" 或 "cherry" 中的任何一个
[] 方括号内放置字符范围,表示匹配方括号中列出的任何字符。 [aeiou] 匹配元音字母 "a"、"e"、"i"、"o" 或 "u" 中的任何一个。
[a-z] 在方括号内使用连字符 - 表示一个字符范围,匹配从 "a" 到 "z" 之间的任何小写字母。 [a-z] 匹配任何小写字母。
[A-Z] 类似地,匹配从 "A" 到 "Z" 之间的任何大写字母。 [A-Z] 匹配任何大写字母。
[0-9] 匹配从 "0" 到 "9" 之间的任何数字。 [0-9] 匹配任何数字。
[^] 在方括号内放置字符范围的开头使用脱字符 ^ 表示反义,即匹配不在方括号中列出的字符。 [^aeiou] 匹配任何非元音字母。

这些范围元字符非常有用,因为它们允许你精确地定义要匹配的字符范围,从而更灵活地处理不同类型的文本。

添加管道符 | 表示或的关系通常用于选择多个备选项之一,而不是范围元字符的一部分。范围元字符主要用于指定字符范围,而 | 主要用于选择不同的模式或备选项。

String regex = "apple|banana|cherry";

这将匹配文本中包含 "apple"、"banana" 或 "cherry" 中的任何一个。

总结来说,[] 是用于定义字符范围的元字符,而 | 是用于选择多个备选项之一的元字符。这两者在正则表达式中有不同的用途。

我们使用java验证以上内容如下:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 验证正则表达式中的范围元字符和备选项
 * @author: zhangyanfeng
 * @create: 2023-11-05 13:39
 **/
public class RangeRegexExample {
    public static void main(String[] args) {
        // 使用正则表达式验证范围元字符和备选项

        // 创建匹配器并打印结果
        MatcherWithInfo("1. Matches for '[aeiou]':", "Hello, world!", "[aeiou]");
        MatcherWithInfo("2. Matches for '[a-z]':", "The quick brown fox", "[a-z]");
        MatcherWithInfo("3. Matches for '[0-9]':", "12345 and 67890", "[0-9]");
        MatcherWithInfo("4. Matches for 'apple|banana|cherry':", "I like cherry pie.", "apple|banana|cherry");
        MatcherWithInfo("5. Matches for '[^aeiou]':", "This is a test.", "[^aeiou]");
    }

    // 匹配器并打印结果
    private static void MatcherWithInfo(String header, String text, String regex) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        StringBuilder matches = new StringBuilder();

        while (matcher.find()) {
            matches.append(matcher.group()).append(" ");
        }

        System.out.println(header);
        System.out.println("   Original Text: " + text);
        System.out.println("   Matching Result: " + (matches.length() > 0 ? matches.toString().trim() : "No Match"));
        System.out.println();
    }
}

验证结果如下:

1. Matches for '[aeiou]':
   Original Text: Hello, world!
   Matching Result: e o o

2. Matches for '[a-z]':
   Original Text: The quick brown fox
   Matching Result: h e q u i c k b r o w n f o x

3. Matches for '[0-9]':
   Original Text: 12345 and 67890
   Matching Result: 1 2 3 4 5 6 7 8 9 0

4. Matches for 'apple|banana|cherry':
   Original Text: I like cherry pie.
   Matching Result: cherry

5. Matches for '[^aeiou]':
   Original Text: This is a test.
   Matching Result: T h s   s     t s t .

综合练习

当使用正则表达式来表示手机号时,可以按照你提供的规则,编写一个更严谨的正则表达式。根据你的规则,手机号的格式可以表示为 1[3456789]\d{9},其中:

  • 1 表示第1位固定为数字1。
  • [3456789] 表示第2位可以是数字3、4、5、6、7、8或9中的任何一个。
  • \d{9} 表示第3位到第11位可以是0-9中的任意数字,并且总共需要匹配9个数字。

下面是一个使用该正则表达式的 Java 示例:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 手机号的格式可以表示为 1[3456789]\d{9}
 * @author: zhangyanfeng
 * @create: 2023-11-05 13:59
 **/
public class PhoneNumberValidation {
    public static void main(String[] args) {
        String regex = "1[3456789]\\d{9}";

        String[] phoneNumbers = {
                "13912345678",
                "18887654321",
                "12345678901",
                "135",
                "1891234",
                "158888888888"
        };

        for (String phoneNumber : phoneNumbers) {
            boolean isMatch = validatePhoneNumber(phoneNumber, regex);
            System.out.println(phoneNumber + " is a valid phone number: " + isMatch);
        }
    }

    private static boolean validatePhoneNumber(String phoneNumber, String regex) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(phoneNumber);
        return matcher.matches();
    }
}

验证结果如下:

13912345678 is a valid phone number: true
18887654321 is a valid phone number: true
12345678901 is a valid phone number: false
135 is a valid phone number: false
1891234 is a valid phone number: false
158888888888 is a valid phone number: false

(二)贪婪、非贪婪与独占模式

在正则表达式中,贪婪、非贪婪和独占模式是用来控制量词(如 *+?{n,m} 等)的匹配方式的不同选项。它们影响了正则表达式的匹配行为,以及在发现多个匹配时如何选择。这些模式用于控制量词的匹配方式,根据不同的需求和性能考虑,可以选择使用贪婪、非贪婪或独占模式来匹配文本。通常,贪婪模式是默认的,非贪婪模式和独占模式用于更精确地控制匹配行为。

1.贪婪模式

婪模式是默认的模式,它会尽可能多地匹配文本,使量词匹配尽量多的字符。例如,正则表达式 a+ 贪婪地匹配尽可能多的连续字符 "a" 直到找不到更多的 "a" 为止。

贪婪模式通常在需要匹配尽可能多的文本时使用。以下是一些使用贪婪模式的实际案例分析:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用贪婪模式的实际案例分析
 * @author: zhangyanfeng
 * @create: 2023-11-05 14:14
 **/
public class GreedyExample {
    public static void main(String[] args) {
        // 案例 1: 提取段落文本
        String paragraphText = "<p>段落 1</p> <p>段落 2</p> <p>段落 3</p>";
        Pattern paragraphPattern = Pattern.compile("<p>(.*?)</p>");
        Matcher paragraphMatcher = paragraphPattern.matcher(paragraphText);

        System.out.println("案例 1: 提取段落文本\n文本: " + paragraphText);

        int paragraphNumber = 1;
        while (paragraphMatcher.find()) {
            String paragraph = paragraphMatcher.group(1);
            System.out.println("段落 " + paragraphNumber + ": " + paragraph);
            paragraphNumber++;
        }

        // 案例 2: 提取注释内容
        String code = "/* 这是一个注释 */ int x = 10; /* 另一个注释 */";
        Pattern commentPattern = Pattern.compile("/\\*(.*?)\\*/");
        Matcher commentMatcher = commentPattern.matcher(code);

        System.out.println("\n案例 2: 提取注释内容\n代码: " + code);

        while (commentMatcher.find()) {
            String comment = commentMatcher.group(1);
            System.out.println("注释: " + comment);
        }

        // 案例 3: 匹配 HTML 标签
        String htmlText = "<div class=\"container\">这是一个 <span>示例</span> HTML 文档。</div>";
        Pattern htmlPattern = Pattern.compile("<(.*?)>");
        Matcher htmlMatcher = htmlPattern.matcher(htmlText);

        System.out.println("\n案例 3: 匹配 HTML 标签\nHTML 文本: " + htmlText);

        while (htmlMatcher.find()) {
            String htmlTag = htmlMatcher.group(1);
            System.out.println("HTML 标签: " + htmlTag);
        }
    }
}

验证结果展示:

案例 1: 提取段落文本
文本: <p>段落 1</p> <p>段落 2</p> <p>段落 3</p>
段落 1: 段落 1
段落 2: 段落 2
段落 3: 段落 3

案例 2: 提取注释内容
代码: /* 这是一个注释 */ int x = 10; /* 另一个注释 */
注释:  这是一个注释 
注释:  另一个注释 

案例 3: 匹配 HTML 标签
HTML 文本: <div class="container">这是一个 <span>示例</span> HTML 文档。</div>
HTML 标签: div class="container"
HTML 标签: span
HTML 标签: /span
HTML 标签: /div

贪婪模式通常用于需要匹配完整块的情况,它会尽可能多地匹配文本,以便提取完整的内容。然而,需要谨慎使用贪婪模式,以避免匹配过多的文本,导致匹配不符合预期。在特定情况下,非贪婪模式或独占模式可能更合适。

2.非贪婪模式(懒惰模式)

 非贪婪模式允许最小匹配,它会尽可能少地匹配文本。非贪婪模式使用 ? 后缀来表示。例如,正则表达式 a+? 非贪婪地匹配尽可能少的 "a"。

假设你有一个包含多个 HTML 链接的文本,例如:

<a href="https://example.com">Example 1</a> <a href="https://example2.com">Example 2</a>

你想提取每个链接的 URL 和链接文本。在这种情况下,非贪婪模式非常有用,因为每个链接的 URL 和文本都在一对标签中。

以下是一个示例 Java 代码,演示如何使用非贪婪模式提取链接:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用非贪婪模式提取链接
 * @author: zhangyanfeng
 * @create: 2023-11-05 14:25
 **/
public class NonGreedyExample {
    public static void main(String[] args) {
        String htmlText = "<a href=\"https://example.com\">Example 1</a> <a href=\"https://example2.com\">Example 2</a>";
        Pattern pattern = Pattern.compile("<a href=\"(.*?)\">(.*?)</a>");
        Matcher matcher = pattern.matcher(htmlText);

        while (matcher.find()) {
            String linkUrl = matcher.group(1);
            String linkText = matcher.group(2);
            System.out.println("URL: " + linkUrl);
            System.out.println("Link Text: " + linkText);
        }
    }
}

在这个案例中,我们使用正则表达式<a href=\"(.*?)\">(.*?)</a>,其中 (.*?) 表示非贪婪模式。结果如下:

URL: https://example.com
Link Text: Example 1
URL: https://example2.com
Link Text: Example 2

非贪婪模式(懒惰模式)在正则表达式中的使用需要根据具体情况和需求来决定。以下是一些使用非贪婪模式的建议分析:

  • 提取最小单元:非贪婪模式适用于需要提取文本中的最小单元的情况。例如,提取 HTML 标签中的文本、提取链接、提取注释等。它会尽量匹配最短的文本片段。

  • 避免过度匹配:在某些情况下,贪婪模式可能会导致过度匹配,匹配整个文本而不是所需的部分。使用非贪婪模式可以避免这种问题,只匹配所需的最小部分。

  • 性能考虑:非贪婪模式可能需要更多的计算资源,因为它会不断尝试匹配更短的文本。在处理大量文本或具有重复模式的文本时,性能可能受到影响。要注意性能问题。

3.独占模式

独占模式是一种相对较新的正则表达式特性,用 ++ 表示。它会尽可能多地匹配文本,但不允许回溯。回溯是在匹配失败后,重新尝试不同的组合来找到匹配的一部分。独占模式不允许回溯,这使得匹配更加高效。例如,正则表达式 a++ 独占地匹配尽可能多的 "a",但不允许回溯。

对于 Java 正则表达式来说,它不支持独占模式,所以要实现非回溯的匹配,需要使用非贪婪模式。非贪婪模式可以帮助你实现不回溯的匹配。我深感抱歉之前的回答有误。

(三)分组与引用

分组与引用是正则表达式中的重要概念,它们允许你在正则表达式中标记、捕获和引用子表达式的匹配结果。

分组是正则表达式中的一个重要概念,它允许你将一个或多个子表达式组合在一起,并将它们视为一个整体。分组有多种用途,包括捕获子表达式的匹配结果、定义重复次数、应用修饰符等。

1.捕获分组(Capturing Group)

  • 定义:使用圆括号 () 可以创建捕获分组。
  • 作用:捕获分组允许你标记和捕获子表达式的匹配结果,以便后续操作使用。被捕获的内容可以在匹配后提取和引用。
  • 编号:每个分组都有一个编号,从左到右从 1 开始递增。你可以使用编号来引用和操作分组中的内容。

假设你有一串文本,其中包含日期,并且日期的格式为 "月/日/年",例如 "12/25/2022"。你想从文本中提取日期,并分别捕获月、日和年。

(\d{1,2})/(\d{1,2})/(\d{4})

在这个正则表达式中,我们使用了三个捕获分组,分别捕获月、日和年。下面是各个分组的说明:

  1. (\d{1,2}):第一个捕获分组,用于捕获月份。这里 \d{1,2} 匹配一个或两个数字,表示月份。
  2. /:匹配日期中的斜杠分隔符。
  3. (\d{1,2}):第二个捕获分组,用于捕获日期(日)。同样,\d{1,2} 匹配一个或两个数字。
  4. /:再次匹配斜杠分隔符。
  5. (\d{4}):第三个捕获分组,用于捕获年份。\d{4} 匹配四个数字,表示年份。

在正则表达式中,捕获分组用括号 () 创建。每个捕获分组都有一个编号,从左到右从 1 开始递增。在这个示例中,我们有三个捕获分组,它们分别捕获了月、日和年。验证如下:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用捕获分组来提取日期信息
 * @author: zhangyanfeng
 * @create: 2023-11-05 14:57
 **/
public class CapturingGroupExample {
    public static void main(String[] args) {
        String text = "Today's date is 12/25/2022, and tomorrow is 01/01/2023.";

        // 定义匹配日期的正则表达式模式
        String pattern = "(\\d{1,2})/(\\d{1,2})/(\\d{4})";

        // 编译正则表达式模式
        Pattern regex = Pattern.compile(pattern);

        // 创建匹配器对象
        Matcher matcher = regex.matcher(text);

        // 循环查找匹配的日期
        while (matcher.find()) {
            // 使用捕获分组提取月、日和年
            String month = matcher.group(1);
            String day = matcher.group(2);
            String year = matcher.group(3);

            // 打印提取的日期信息
            System.out.println("Month: " + month);
            System.out.println("Day: " + day);
            System.out.println("Year: " + year);
        }
    }
}

2.非捕获分组(Non-Capturing Group)

  • 定义:使用 (?:...) 可以创建非捕获分组。
  • 作用:非捕获分组与捕获分组类似,但不会生成捕获的结果。它主要用于分组表达式,而不需要捕获匹配结果。
  • 语法(?:pattern),其中 pattern 是子表达式。

假设你有一个文本,其中包含电子邮件地址,你想匹配电子邮件地址,但只对域名部分感兴趣,而不关心用户名。你可以使用非捕获分组来实现这个目标。

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用非捕获分组
 * @author: zhangyanfeng
 * @create: 2023-11-05 15:01
 **/
public class NonCapturingGroupExample {
    public static void main(String[] args) {
        String text = "Contact us at support@example.com or info@company.com";

        // 定义匹配电子邮件地址域名的正则表达式模式
        String pattern = "(?<=@)(?:[a-zA-Z0-9.-]+\\.)+[a-zA-Z]{2,4}";

        // 编译正则表达式模式
        Pattern regex = Pattern.compile(pattern);

        // 创建匹配器对象
        Matcher matcher = regex.matcher(text);

        // 查找匹配的电子邮件地址域名
        while (matcher.find()) {
            String domainName = matcher.group();
            System.out.println("Found domain: " + domainName);
        }
    }
}

在这个示例中,我们使用了非捕获分组"(?<=@)(?:[a-zA-Z0-9.-]+\\.)+[a-zA-Z]{2,4}"来匹配电子邮件地址的域名部分。这个非捕获分组用于分组表达式,但不会捕获域名部分的匹配结果。因此,当我们查找匹配的电子邮件地址时,只会得到完整的电子邮件地址而不包括用户名。

3.命名捕获分组(Named Capturing Group)

  • 定义:一些正则表达式引擎支持命名捕获分组。它允许为分组指定名称,以便更容易引用和理解。
  • 语法(?<name>pattern),其中 name 是分组的名称,pattern 是子表达式。

假设你有一串文本,其中包含日期,并且日期的格式为 "月/日/年",例如 "12/25/2022"。你想从文本中提取日期,并使用命名捕获分组分别捕获月、日和年。

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用命名捕获分组分别捕获月、日和年
 * @author: zhangyanfeng
 * @create: 2023-11-05 15:08
 **/
public class NamedCapturingGroupExample {
    public static void main(String[] args) {
        String text = "Today's date is 12/25/2022, and tomorrow is 01/01/2023.";

        // 定义匹配日期的正则表达式模式,使用命名捕获分组
        String pattern = "(?<month>\\d{1,2})/(?<day>\\d{1,2})/(?<year>\\d{4})";

        // 编译正则表达式模式
        Pattern regex = Pattern.compile(pattern);

        // 创建匹配器对象
        Matcher matcher = regex.matcher(text);

        // 查找匹配的日期
        while (matcher.find()) {
            // 使用命名捕获分组提取月、日和年
            String month = matcher.group("month");
            String day = matcher.group("day");
            String year = matcher.group("year");

            // 打印提取的日期信息
            System.out.println("Month: " + month);
            System.out.println("Day: " + day);
            System.out.println("Year: " + year);
        }
    }
}

在这个示例中,我们使用了命名捕获分组 (?<name>pattern) 来为捕获的结果指定名称。这里分别使用了 "month"、"day" 和 "year" 作为名称。然后,我们可以使用 matcher.group("name") 来获取相应命名捕获分组的结果。

4. 引用(Backreference)

  • 定义:引用允许你在正则表达式中引用之前捕获的内容。
  • 语法:使用 \n 来引用分组的编号,其中 n 是分组的编号。
  • 作用:引用的主要作用是在正则表达式中重用相同的文本。它通常用于查找重复出现的文本或确保两个地方的文本匹配相同。

假设你想匹配连续出现的相同单词。例如,你希望找到文本中连续重复的单词,如 "apple apple"、"cat cat cat" 等。

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 找到文本中连续重复的单词
 * @author: zhangyanfeng
 * @create: 2023-11-05 15:13
 **/
public class BackreferenceExample {
    public static void main(String[] args) {
        String text = "This is an example of repeated words: apple apple, cat cat cat, and dog dog dog dog dog.";

        // 定义匹配连续重复单词的正则表达式模式
        String pattern = "\\b(\\w+)(?: \\1)+\\b";

        // 编译正则表达式模式
        Pattern regex = Pattern.compile(pattern);

        // 创建匹配器对象
        Matcher matcher = regex.matcher(text);

        // 查找匹配的连续重复单词
        while (matcher.find()) {
            String repeatedWords = matcher.group();
            System.out.println("Found repeated words: " + repeatedWords);
        }
    }
}

在这个示例中,正则表达式 \\b(\\w+) \\1\\b 的解释如下:

  • \\b 表示单词边界,确保只匹配整个单词。
  • (\\w+) 是第一个捕获分组,匹配一个或多个单词字符。
  • \\1 表示引用第一个捕获分组,用来匹配与第一个捕获分组相同的文本。

这个示例会匹配文本中连续重复的单词,并输出它们,例如 "apple apple"、"cat cat cat" 和 "dog dog dog dog dog"。

这个示例会匹配文本中连续重复的单词,并输出它们。使用引用可以轻松处理这种情况,而不需要显式编写每个可能的单词。这在查找重复文本或特定模式的重复出现时非常有用。

5. 重复分组

  • 你可以使用分组来定义重复出现的子表达式。例如,(abc){3} 匹配 "abcabcabc"。
package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 重复分组
 * @author: zhangyanfeng
 * @create: 2023-11-05 15:19
 **/
public class RepeatedGroupExample {
    public static void main(String[] args) {
        String text = "rfABCDGHabcabcabcJMMMKJBGHJNhgjjhkj";

        // 定义匹配重复的子表达式的正则表达式模式
        String pattern = "(abc){3}";

        // 编译正则表达式模式
        Pattern regex = Pattern.compile(pattern);

        // 创建匹配器对象
        Matcher matcher = regex.matcher(text);

        // 查找匹配的文本
        while (matcher.find()) {
            String matchedText = matcher.group();
            System.out.println("Found: " + matchedText);
        }
    }
}

(四)匹配模式

1.不区分大小写

匹配模式中的不区分大小写模式允许你创建正则表达式,它会匹配文本时不区分字符的大小写。这在需要匹配大小写不敏感的文本时非常有用。在正则表达式中,可以使用 (?i) 开启不区分大小写模式,或使用 (?-i) 关闭它。

  • 启用不区分大小写模式: 通过在正则表达式的开头添加 (?i) 来启用不区分大小写模式。例如,正则表达式 (?i)apple 将匹配文本中的 "apple"、"Apple"、"aPPle" 等各种大小写形式。

  • 禁用不区分大小写模式: 通过在正则表达式的开头添加 (?-i) 来禁用不区分大小写模式。在不区分大小写模式被禁用后,正则表达式将严格区分字符的大小写。

不区分大小写模式对于需要匹配文本中的标识符、关键字、名称或其他文本,而不关心其大小写形式时非常有用。例如,如果你要匹配一个用户名,用户可能以不同的大小写形式输入,但你希望都能匹配。

以下是一个示例,演示如何使用不区分大小写模式匹配用户名:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用不区分大小写模式匹配用户名
 * @author: zhangyanfeng
 * @create: 2023-11-05 15:25
 **/
public class CaseInsensitiveExample {
    public static void main(String[] args) {
        String text = "Welcome, uSer123!";

        // 启用不区分大小写模式,匹配用户名
        Pattern pattern = Pattern.compile("(?i)user123");
        Matcher matcher = pattern.matcher(text);

        if (matcher.find()) {
            System.out.println("Matched: " + matcher.group());
        } else {
            System.out.println("No match");
        }
    }
}

在上面的示例中,不区分大小写模式允许正则表达式匹配 "user123",尽管文本中的 "uSer123" 使用不同的大小写。

不区分大小写模式是处理大小写不敏感匹配需求的有用工具,它可以帮助你更容易地编写灵活的正则表达式。

2.点号通配模式

匹配模式中的点号通配模式(Dot-All Mode,Single-Line Mode)允许正则表达式中的点号 . 匹配任何字符,包括换行符。默认情况下,点号 . 只匹配除换行符外的字符。点号通配模式用于需要跨越多行匹配文本的情况。

  • 启用点号通配模式: 通过在正则表达式的开头添加 (?s) 来启用点号通配模式。点号通配模式使正则表达式中的点号 . 能够匹配包括换行符在内的任何字符。

  • 禁用点号通配模式: 通过在正则表达式的开头添加 (?-s) 来禁用点号通配模式。在禁用点号通配模式后,点号 . 将只匹配除换行符外的字符,与默认行为相同。

点号通配模式通常用于处理包含多行文本的情况,例如处理包含换行符的段落、HTML 标签、或需要匹配多行注释的文本。

以下是一个示例,演示如何使用点号通配模式匹配跨越多行的 HTML 标签内容:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用点号通配模式匹配跨越多行的 HTML 标签内容
 * @author: zhangyanfeng
 * @create: 2023-11-05 15:30
 **/
public class DotAllModeExample {
    public static void main(String[] args) {
        String html = "<div>\nThis is some text inside a div.\n</div>";

        // 启用点号通配模式,匹配 div 标签内容
        Pattern pattern = Pattern.compile("<div>(.*?)</div>", Pattern.DOTALL);
        Matcher matcher = pattern.matcher(html);

        if (matcher.find()) {
            String divContent = matcher.group(1);
            System.out.println("Div content: " + divContent);
        } else {
            System.out.println("No match");
        }
    }
}

在上面的示例中,点号通配模式允许正则表达式匹配包括换行符在内的多行 HTML <div> 标签内容。

实际上,Java 中可以通过两种方式来启用点号通配模式:

使用 (?s):直接在正则表达式中的开头添加 (?s),如下所示:

Pattern pattern = Pattern.compile("(?s)<div>(.*?)</div>");

使用 Pattern.DOTALL 标志:在编译正则表达式时,通过传递 Pattern.DOTALL 标志来启用点号通配模式,如下所示:

Pattern pattern = Pattern.compile("<div>(.*?)</div>", Pattern.DOTALL);

两种方法都可以实现点号通配模式。在前一个示例中,我使用了第二种方法,使用 Pattern.DOTALL 标志,以演示点号通配模式的应用。如果你更喜欢在正则表达式中直接添加 (?s),你可以按照第一种方法来使用它。

点号通配模式在处理需要跨越多行的文本匹配时非常有用,它使点号 . 具有更广泛的匹配能力。

3.多行模式

多行模式是一种正则表达式匹配模式,它主要影响 ^$ 这两个锚点的匹配行为。默认情况下,^ 匹配字符串的开头,而 $ 匹配字符串的结尾。启用多行模式后,这些锚点将匹配文本的每一行的开头和结尾。

  • 启用多行模式: 通过在正则表达式的开头添加 (?m) 来启用多行模式。多行模式使正则表达式中的 ^$ 锚点匹配每行文本的开头和结尾。

  • 禁用多行模式: 如果不需要多行匹配模式,可以在正则表达式的开头添加 (?-m) 来禁用它。在禁用多行模式后,^$ 仍然只匹配整个字符串的开头和结尾。

多行模式通常用于处理包含多行文本的情况,例如处理文本文件中的多行注释、日志文件或需要逐行处理的文本数据。

以下是一个示例,演示如何使用多行模式匹配包含多行注释的文本:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用多行模式匹配包含多行注释的文本
 * @author: zhangyanfeng
 * @create: 2023-11-05 15:37
 **/
public class MultilineModeExample {
    public static void main(String[] args) {
        String text = "This is a sample text.\n" +
                "/* This is a multiline\n" +
                "   comment. */\n" +
                "More text.";

        // 启用多行模式,匹配多行注释内容
        Pattern pattern = Pattern.compile("/\\*.*?\\*/", Pattern.MULTILINE | Pattern.DOTALL);
        Matcher matcher = pattern.matcher(text);

        while (matcher.find()) {
            String comment = matcher.group();
            System.out.println("Multiline Comment: " + comment);
        }
    }
}

在上面的示例中,多行模式使正则表达式匹配多行注释内容,而不仅仅匹配整个文本的开头和结尾。多行模式是一种有用的匹配模式,它允许你更精确地处理多行文本。

4.注释模式

匹配模式中的注释模式(Comments Mode)允许你在正则表达式中添加注释,以提高正则表达式的可读性和维护性。注释模式允许你在正则表达式中添加注释以解释模式的目的和行为,而这些注释不会影响匹配的结果。

  • 启用注释模式: 在正则表达式中使用 (?x)(?# ... ) 来启用注释模式。(?x) 告诉正则表达式引擎要忽略空白字符(除了在字符类内部),并且允许添加注释。你可以使用 (?# ... ) 添加注释,其中 ... 是你希望添加的注释内容。

  • 注释内容:(?# ... ) 中的注释内容可以是任何描述性文本,用于解释正则表达式的目的、匹配模式或任何你认为有助于理解和维护正则表达式的信息。注释内容不会影响匹配结果,它们仅用于文档和可读性。

注释模式通常用于复杂的正则表达式,以帮助其他人或你自己更容易地理解和维护正则表达式。注释模式使正则表达式更易于阅读,特别是在包含多个子表达式和匹配模式的情况下。

以下是一个示例,演示如何使用注释模式添加注释来解释正则表达式的含义:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用注释模式添加注释来解释正则表达式的含义
 * @author: zhangyanfeng
 * @create: 2023-11-05 15:41
 **/
public class CommentsModeExample {
    public static void main(String[] args) {
        String text = "Hello, John! My name is ZYF";

        // 启用注释模式,匹配问候语
        Pattern pattern = Pattern.compile(
                "(?x)        # 启用注释模式\n" +
                        "Hello,      # 匹配 Hello\n" +
                        "\\s         # 匹配一个空白字符\n" +
                        "John        # 匹配 John"
        );
        Matcher matcher = pattern.matcher(text);

        if (matcher.find()) {
            System.out.println("Matched: " + matcher.group());
        } else {
            System.out.println("No match");
        }
    }
}

在上面的示例中,注释模式通过 (?x) 启用,允许在正则表达式中添加注释,以更清晰地解释模式的目的。注释模式是一种有用的工具,它提高了正则表达式的可读性和维护性,特别是在编写复杂的正则表达式时。

(五)断言(Assertion)

正则表达式中的断言(Assertion)是一种特殊的匹配条件,它允许你指定匹配目标的位置而不是实际字符。最常见的断言有三种:单词的边界断言、行的开始或结束断言,以及环视断言。断言是非常有用的工具,它允许你精确控制匹配的位置和条件,以满足特定的匹配需求。不同类型的断言可以用于不同的情况,使正则表达式更强大和灵活。

1.单词的边界

单词的边界断言是正则表达式中的一种断言,用于匹配单词的开始或结束位置,而不是整个单词。它通常使用特殊字符 \b 表示单词的边界,或使用 \B 表示非单词的边界。

  • \b: \b 是单词的边界断言,它匹配单词的开始或结束位置。单词被定义为由字母、数字和下划线组成的字符序列。这意味着 \b 匹配单词的起始和结束,以及单词之间的位置。例如,正则表达式 \bword\b 匹配整个单词 "word",但不匹配 "sword" 或 "words" 中的 "word"。

  • \B: \B 是非单词的边界断言,它匹配不是单词的起始或结束位置。换句话说,它匹配单词内部的位置,或者非单词字符的位置。例如,正则表达式 \Bword\B 匹配 "sword" 或 "words" 中的 "word",但不匹配整个单词 "word"。

单词的边界断言通常用于需要精确匹配整个单词的情况,而不是部分匹配。它在文本处理中常用于查找特定单词或标识符。以下是一些示例用法:

  • 匹配一个特定单词(例如,\bapple\b 匹配整个单词 "apple")。
  • 查找文本中的标识符或关键字(例如,\bif\b 匹配单词 "if" 作为关键字)。
  • 分隔文本为单词或标识符(使用 \b\w+\b 可以提取文本中的所有单词或标识符)。

单词的边界断言是正则表达式中非常有用和常见的工具,用于精确匹配文本中的单词或标识符。

在Java中使用单词边界断言来验证和匹配文本:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用单词边界断言来验证和匹配文本
 * @author: zhangyanfeng
 * @create: 2023-11-05 16:16
 **/
public class WordBoundaryExample {
    public static void main(String[] args) {
        // 文本示例
        String text = "The quick brown fox jumps over the lazy dog.";

        // 使用\b断言匹配整个单词 "fox"
        Pattern pattern1 = Pattern.compile("\\bfox\\b");
        Matcher matcher1 = pattern1.matcher(text);

        while (matcher1.find()) {
            System.out.println("Match: " + matcher1.group());
        }

        // 使用\B断言匹配非单词边界的 "fox"
        Pattern pattern2 = Pattern.compile("\\Bfox\\B");
        Matcher matcher2 = pattern2.matcher(text);

        while (matcher2.find()) {
            System.out.println("Non-Match: " + matcher2.group());
        }
    }
}

2.行的开始或结束

断言是正则表达式中的一种特殊元字符,用于指定匹配的位置而不匹配实际字符。行的开始和行的结束断言用于匹配文本的开头和结尾。以下是关于行的开始和行的结束断言的详细介绍:

行的开始断言 ^

  • ^ 是行的开始断言,用于匹配文本的开头。
  • 例如,正则表达式 ^apple 匹配以 "apple" 开头的文本。
  • 示例:^apple 匹配 "apple pie" 中的 "apple",但不匹配 "pineapple"。

行的结束断言 $

  • $ 是行的结束断言,用于匹配文本的结尾。
  • 例如,正则表达式 apple$ 匹配以 "apple" 结尾的文本。
  • 示例:apple$ 匹配 "caramel apple" 中的 "apple",但不匹配 "apple pie"。

这些行的开始和行的结束断言非常有用,因为它们允许精确地控制匹配的位置。它们通常用于查找或验证文本的起始或终止部分,例如查找文件中以特定字符串开头或结尾的行。

下面是一个示例,演示如何在Java中使用行的开始和行的结束断言:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用行的开始和行的结束断言
 * @author: zhangyanfeng
 * @create: 2023-11-05 16:28
 **/
public class LineBoundaryExample {
    public static void main(String[] args) {
        // 文本示例
        String text = "apple\nbanana\ncherry\norange";

        // 使用^断言匹配行的开始
        Pattern pattern1 = Pattern.compile("^apple", Pattern.MULTILINE);
        Matcher matcher1 = pattern1.matcher(text);

        while (matcher1.find()) {
            System.out.println("Match at line start: " + matcher1.group());
        }

        // 使用$断言匹配行的结束
        Pattern pattern2 = Pattern.compile("cherry$", Pattern.MULTILINE);
        Matcher matcher2 = pattern2.matcher(text);

        while (matcher2.find()) {
            System.out.println("Match at line end: " + matcher2.group());
        }
    }
}

在这个示例中,我们使用 ^ 断言来匹配以 "apple" 开头的行,并使用 $ 断言来匹配以 "cherry" 结尾的行。运行代码,你将看到以下输出:

Match at line start: apple
Match at line end: cherry

这表明成功匹配了行的开头和行的结尾。可以根据需要修改正则表达式模式以匹配不同的文本部分。

3.环视(Lookaround)

环视(Lookaround)是正则表达式中的一种高级特性,用于在匹配时查看前面或后面是否满足某种条件,但实际上不匹配这些条件。环视分为正向环视和负向环视,用于查看某些条件是否存在或不存在。

正向环视(Positive Lookaround):正向环视用于查看前面或后面是否满足某种条件,并且只有在满足条件时才匹配成功。正向环视分为正向预查(Positive Lookahead)和正向回顾(Positive Lookbehind)两种情况。

  • 正向预查(Positive Lookahead):正向预查由 (?= ...) 表示,其中 ... 是要查看的条件。它要求紧跟在这个条件后面的内容匹配成功。

  • 正向回顾(Positive Lookbehind):正向回顾由 (?<= ...) 表示,其中 ... 是要查看的条件。它要求紧跟在这个条件前面的内容匹配成功。

负向环视(Negative Lookaround):负向环视用于查看前面或后面是否不满足某种条件,并且只有在不满足条件时才匹配成功。负向环视分为负向预查(Negative Lookahead)和负向回顾(Negative Lookbehind)两种情况。

  • 负向预查(Negative Lookahead):负向预查由 (?! ...) 表示,其中 ... 是要查看的条件。它要求紧跟在这个条件后面的内容不匹配成功。

  • 负向回顾(Negative Lookbehind):负向回顾由 (?<! ...) 表示,其中 ... 是要查看的条件。它要求紧跟在这个条件前面的内容不匹配成功。

环视的主要作用是在匹配时查看前面或后面的文本内容,以便更精确地定位需要的模式。这对于复杂的匹配和替换操作非常有用。

以下是一个 Java 代码示例,演示如何在正则表达式中使用环视(Lookaround)来验证文本中的条件:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 使用环视(Lookaround)来验证文本中的条件
 * @author: zhangyanfeng
 * @create: 2023-11-05 16:38
 **/
public class LookaroundExample {
    public static void main(String[] args) {
        // 文本示例
        String text = "The quick brown fox jumps over the lazy dog.";

        // 正向预查示例:匹配包含 "fox" 后面是 "jumps" 的情况
        Pattern pattern1 = Pattern.compile("fox(?= jumps)");
        Matcher matcher1 = pattern1.matcher(text);

        while (matcher1.find()) {
            System.out.println("Positive Lookahead: " + matcher1.group());
        }

        // 负向预查示例:匹配包含 "fox" 后面不是 "lazy" 的情况
        Pattern pattern2 = Pattern.compile("fox(?! lazy)");
        Matcher matcher2 = pattern2.matcher(text);

        while (matcher2.find()) {
            System.out.println("Negative Lookahead: " + matcher2.group());
        }

        // 正向回顾示例:匹配包含 "fox" 前面是 "quick" 的情况
        text = "The quick fox jumps over the lazy dog.";
        Pattern pattern3 = Pattern.compile("(?<=quick )fox");
        Matcher matcher3 = pattern3.matcher(text);

        while (matcher3.find()) {
            System.out.println("Positive Lookbehind: " + matcher3.group());
        }

        // 负向回顾示例:匹配包含 "fox" 前面不是 "brown" 的情况
        Pattern pattern4 = Pattern.compile("(?<!brown )fox");
        Matcher matcher4 = pattern4.matcher(text);

        while (matcher4.find()) {
            System.out.println("Negative Lookbehind: " + matcher4.group());
        }
    }
}

这个示例展示了正向预查、负向预查、正向回顾和负向回顾的使用。

(六)正则转义

正则表达式中的转义用于指示正则表达式引擎将某些字符解释为特殊字符而不是普通字符。正则表达式中的特殊字符通常用于表示匹配规则和模式。以下是一些常见的正则表达式中需要转义的特殊字符:

  1. \: 反斜杠(\)用于转义紧跟其后的字符,使其变成普通字符。例如,\. 表示匹配点号而不是匹配任何字符。

  2. ^: 插入符(^)通常用于表示匹配行的开头,但在字符类(方括号内)中,它表示反义。要匹配插入符字符本身,需要使用 \^

  3. $: 美元符号($)通常用于表示匹配行的结尾。要匹配美元符号字符本身,需要使用 \$

  4. .: 点号(.)通常用于表示匹配除换行符之外的任何字符。要匹配点号字符本身,需要使用 \.

  5. *, +, ?, {, }: 这些字符通常用于表示量词,指定前面的字符可以重复多少次。要匹配这些字符本身,需要使用 \*, \+, \?, \{, \}

  6. |: 竖线(|)通常用于表示或操作,匹配两个选项中的一个。要匹配竖线字符本身,需要使用 \|

  7. (): 圆括号通常用于分组表达式。要匹配圆括号字符本身,需要使用 \(\)

  8. []: 方括号通常用于定义字符类,匹配其中的任何字符。要匹配方括号字符本身,需要使用 \[\]

  9. \: 反斜杠本身需要进行转义,因此要匹配反斜杠字符本身,需要使用 \\

要使用这些特殊字符作为普通字符进行匹配,只需在它们前面添加反斜杠进行转义。这样正则表达式引擎会将它们视为普通字符而不是特殊字符。

以下是一个Java类,用于验证正则表达式中的转义字符:

package org.zyf.javabasic.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: zyfboot-javabasic
 * @description: 验证正则表达式中的转义字符
 * @author: zhangyanfeng
 * @create: 2023-11-05 16:48
 **/
public class RegexEscapeExample {
    public static void main(String[] args) {
        // 要匹配的文本
        String text = "This is a dot (.) and a question mark (?).";

        // 使用正则表达式匹配点号和问号,需要转义这两个字符
        String regex = "\\.";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);

        System.out.println("Matching dots:");
        while (matcher.find()) {
            System.out.println("Match: " + matcher.group());
        }

        // 使用正则表达式匹配问号
        regex = "\\?";
        pattern = Pattern.compile(regex);
        matcher = pattern.matcher(text);

        System.out.println("\nMatching question marks:");
        while (matcher.find()) {
            System.out.println("Match: " + matcher.group());
        }
    }
}

在这个示例中,我们创建了一个Java类 RegexEscapeExample,并使用正则表达式来匹配文本中的点号和问号。由于点号和问号在正则表达式中是特殊字符,所以需要使用反斜杠进行转义,即 \\.\\?