如何使用Java的REGEX

119 阅读12分钟

开始使用Java的REGEX

验证字符串输入的需要是一种无处不在的需要,项目要求每个开发人员在他们的编程活动中的某个时刻都要使用。正则表达式,俗称regex,是一种方便的工具,旨在有效验证字符串操作。

正则表达式的应用可能相当令人生畏,特别是对初学者而言。这主要是由于表达式中应用的字符的不寻常或奇怪的组合,使得语法的解释具有挑战性。

正则表达式简介

正则表达式是用来描述搜索模式的字符串,与其他字符串中出现的字符和组合相匹配。

除了搜索一个字符或字符组合外,我们还可以使用正则表达式来。

  • 提取字符串。
  • 删除子串,
  • 编辑字符串。
  • 替换子字符串。
  • 拆分字符串。
  • 以及验证字符串以确保它们符合预定的方案和要求。

在大多数编程语言中,字符串是不可改变的,上面提到的操作需要形成一个新的字符串字面或对象。

为什么是正则表达式?

想象一下,你想验证一个电子邮件地址;你通常可以尝试迭代电子邮件字符串,同时验证电子邮件字符串的每个字符。使用迭代器和IF语句并不是一个实用的解决方案,因为它将导致你的代码更加复杂,并可能降低性能。

然而,通过准确地组合REGEX字符,你至少可以用一行代码来实现这个目标,从而使你的程序更可读,更干净,更可扩展。正则表达式有广泛的用途。人们可以用一个正则表达式来验证各种输入。

许多编程语言都支持正则表达式;你可以用更少的代码行做更多的事情,从而使你的代码更简洁。此外,与应用IF和ELSE语句相比,验证的速度更快。

正则表达式的解释

如前所述,regex结合了简单字符和特殊字符,对字符串进行模式匹配。在 regex 字符串中组成的每个字符,与其他字符相结合,负责执行预期的匹配。

大多数regex字符都是自己匹配的。换句话说,字符'a'如果在一个regex模式中组成,将与输入字符串中的字符'a'进行比较。


    public static void main(String[] args){

        String word = "hello";
        System.out.println(word.matches("hello"));

 //      output: true  
 
    }

在上面的代码中,谓词方法.matches("hello") 接受一个字符串参数,代表一个regex模式,在字符串变量word 上调用,以确定其值hello 与给定的regex模式hello 匹配。

程序输出布尔值true ,因为变量中的字符的长度和顺序与重合模式匹配。当我们对一个字面字符串进行这种匹配时,输出结果是一样的。


    public static void main(String[] args){

        System.out.println("hello".matches("hello"));

//       output: true

    }

特殊字符的加入重新定义了相关字符的解释和重合词的整体模式。其中一些元字符包括但不限于+ * ? \ [ ] ( )

元字符

这些字符在处理重码模式时表示特殊的含义。元字符是特殊的字符,可以独立使用或与其他字符一起使用,以提供一个定义的模式。

元字符根据其功能的不同被分类为,但不限于:

  • 量词
  • 字符转义
  • 字符类别

量词

量词规定了它前面的字符出现的次数。它通常放在一个字符或字符类(将在本文中讨论)之后,以指定该字符或字符类的多少个实例,前面的实例必须存在,以便输入匹配。

  • 零次或多次匹配器 (*)。这被视为零或更多量词,因为它匹配其前面的字符出现任何次数的实例。

下面的例子说明了* 量词的作用。


    public static void main(String[] args){

        System.out.println("o".matches("o*"));
//          output: true

        System.out.println("oooooo".matches("o*"));
//          output: true

        System.out.println("hello".matches("hello*"));
//          output: true

        System.out.println("hell".matches("hello*"));
//          output: true

        System.out.println("hell".matches("o*"));
//          output: false

    }

o* 每个打印语句(除了最后一个打印语句)的输出都是true ,因为在重合表达式* 前面的字符'o'与字符串字面意义o

最后一条打印语句的输出结果是false ,因为该regex模式匹配了0个或更多数量的字符串o ,除此之外没有其他内容。

  • 一个或多个匹配器(+)。这被认为是一个或多个量词,因为它匹配其前面的字符至少出现一次的实例。

下面的例子说明了+ 量词。


    public static void main(String[] args){

        System.out.println("o".matches("o+"));
//          output: true

        System.out.println("oooooo".matches("o+"));
//          output: true

        System.out.println("helloo".matches("hello+"));
//          output: true

        System.out.println("hell".matches("hello+"));
//          output: false

        System.out.println("hell".matches("o+"));
//          output: false

    }

这种情况类似于* 量词,只是它要求它的直接字符有一次或多次出现。

需要注意的是,regex匹配是区分大小写的,因此下面给出的例子中的两行代码都因为大小写不匹配而输出布尔值false


    System.out.println("hello".matches("hellO+"));
//      output: false

    System.out.println("hello".matches("hellO*"));
//      output: false
  • 零或一次性匹配器(?)。这被认为是零或一次量词,因为它最多只能匹配其前面的字符出现一次的实例。

下面的例子说明了? 量词的作用。


    String[] words = {"cat", "care", "cast", "car", "forth", "caree"};
    
    for(String word: words){
        if(word.matches("care?"))
            System.out.print(word + " ");
    }

//      output: care car

在上面的例子中,我们用变量名 "words "遍历字符串数组,并输出与所提供的regex模式匹配的数组元素。

该模式匹配包含字符c,a,r ,以及最多一次出现的字符e 的字符串。最后,得到的结果被并排连接,每个词之间有一个空白。

  • N次匹配器({n})。这被看作是n 量词,其中n 是一个整数,因为它匹配其前面的字符正好出现n次的实例。

下面的例子说明了{n} 量词的作用。


    String words = {"bag", "sheet", "give", "show", "cling", "keep"};

    for(String word: words){
        if(word.matches("ke{2}p"))
            System.out.print(word + " ");
    }

//      output: keep

这段代码的输出是keep ,因为这个词组匹配的是任何元素,这些元素在字符kp 之间正好有两次出现的字符e

  • 至少N次的匹配器({n, } )。{n,} 量词匹配其前面的字符至少出现n 次的实例。

    String words = {"bag", "feed", "give", "show", "cling", "fed"};

    for(String word: words){
        if(word.matches("fe{2,}d"))
            System.out.print(word + " ");
    }

//      output: feed

这个代码的输出是feed ,因为这个重码匹配任何元素,在字符fd 之间至少有两个字符e 的出现。

  • 匹配N到M的次数({n,m})。这个量词匹配其前面的字符在nm 之间出现的次数,其中nm 的值都是整数的实例。

在下面的例子中,该反义词匹配字符串数组words 中的元素。字符串words 包含了2到5个字符的实例0 ,形成一个字符串。


    String regexPattern = "0{2,5}";
    String values = {"00", "0", "000", "210", "0000", "0000000", "00000"};

    for(int i = 0; i < values.length; i++){
        if(values[i].matches(regexPattern)){
            System.out.println(values[i] + " matches the regex pattern and was found at index " + i);

        }
    }

//      The outputs are:
//        00 matches the regex pattern and was found at index 0
//        000 matches the regex pattern and was found at index 2
//        0000 matches the regex pattern and was found at index 4
//        00000 matches the regex pattern and was found at index 6

量词将尽可能多地匹配出现,只要匹配仍然是成功的。由于这个原因,它们被称为Greedy 。当量词被问号(?)取代时,即*? ,量词变得不情愿或懒惰。这导致它尽可能少地匹配出现,只要匹配仍然成功。

字符类

一个字符类指定了一组字符,在一个给定的字符串中,任何一个这样的字符组都应该出现,这样才会发生匹配。一个字符类区分了某些类别的字符,有时与其他字符具有相似的属性。

例如,它将字母与数字、数字与标点符号、特定字母与其他字母等区分开来。

字符说明
.点(.)匹配任何字符,除了换行符\n 和回车符\r
\d该字符匹配任何单一的十进制数值或数字,或数字。
\D匹配任何非数字的单一字符。
\w匹配任何单一的字母数字字符,即字母字符、数字以及下划线字符。_
\W匹配任何非字母数字的单一字符。
\s匹配任何空白字符。
\S匹配任何非空白字符。

字符组和范围

  • 方括号[ ] ,用于包含一个字符范围。

[a-z] 匹配任何小写字母字符。

[A-Z] 匹配任何大写字母字符。

[0-9] 匹配任何单一的数字实例。

[a-zA-Z0-9] 匹配任何单一出现的字母或数字。

\\w+|\\d+ 匹配一个只包含字母数字字符或数字的字符串。

  • 小括号( ) ,按照所定义的确切顺序匹配所包含的字符。

(\\w+\\s\\w+) 匹配hello world

  • 垂直线| 匹配直线两侧的字符或字符组。这可以被解释为regex中的or 操作符。

断言

我们使用断言来指定匹配应该发生的边界。它们也被称为regex锚点。

字符意义
^这确保匹配从字符串或行的开头开始。当我们在字符组或范围的开头使用断言时,它确保除了该组中指定的字符外,其他每个字符都是匹配的。
$这确保了匹配发生在字符串的末尾或换行符之前。
\b这表示一个词的边界,并匹配不在任何词字符之前或之后的字符的出现。例如,字符串anna 与模式\\b\\w+ 匹配,因为在该字符串之前没有任何其他单词字符。但是,它与a\\b 不匹配,因为第一个字符a 紧接在其他单词字符之前。
\B这与非词的边界相匹配,给定的位置是前一个和后一个字符都是词或都是非词。

字符转义('')

反斜杠\ ,用于获得字面意义或价值,通常是一个量词或任何其他特殊字符。例如,为了获得字面意义的反斜杠字符,需要对其进行转义\\

它还表示它后面的字符是特殊的,如本文的字符类部分所述。

Java模式和匹配器API

Java类Pattern表示一个已编译的正则表达式。regex模式是使用Pattern.compile() 方法创建的。Pattern.compile() 是一个重载方法,其第一个或唯一的参数(取决于其方法被调用)是一个字符串。String参数包含要匹配的正则表达式。

Java类Matcher通过解释编译后的regex模式对字符串或字符序列进行匹配操作。



    public static void main(String[] args){
        String statements = """
                Paul's wedding was 09/15/2002
                Today's date is 07/16/2015
                Priscilla graduated on 10/25/20
                """;

        Pattern compiledRegex = Pattern.compile("P.*\\d{1,2}/\\d{1,2}/\\d{1,2}");
        Matcher regexMatcher = compiledRegex.matcher(statements);

        while(regexMatcher.find())
            System.out.println(regexMatcher.group());

//          output: Paul's wedding was 09/15/20
//                  Priscilla graduated on 10/25/20  


    }

Java类Pattern 编译了regex模式,然后创建了Matcher实例,regexMatcher ,它也持有编译后的regex模式的返回值。

Matcher 方法 ,将字符串的一部分与搜索模式相匹配,并输出与搜索模式相匹配的那部分字符串。find

需要注意的是,如果整个字符串与正则表达式模式相匹配,类字符串、模式或匹配器的方法matches 会返回一个布尔值true

一些应用regex的字符串方法

给定字符串的两个实例,其变量名称为sreplacement ,regex模式表示为regex ,我们可以对s 进行以下操作。

  • s.replaceFirst(“regex”, “replacement”):这将用replacement 替换s 中第一次出现的regex

  • s.replaceAll(“regex”, “replacement”):用replacement 替换s 中所有出现的regex

  • s.matches(“regex”):如果s 中的整个字符序列与表示为regex 的方法参数中的regex模式相匹配,则评估s ,并返回true

  • s.split(“regex”):遍历s ,在与regex 匹配的交叉点上,它将s 的组成字符序列分离出来,作为子字符串存储在一个字符串数组中。regex 不包括在结果字符串数组中。

使用regex进行密码模式验证

让我们继续实践我们到目前为止所学到的知识,建立一个regex模式来验证一个给定的密码是否符合以下要求。

  • 该密码至少包含一个大写字符。
  • 密码至少包含一个小写字符。
  • 密码至少包含一个数字。
  • 密码至少包含一个特殊字符。
  • 密码至少有七个字符。

模式的构建应使字符的序列不成为匹配的决定因素;相反,希望重码能检查所需的字符是否存在于给定的密码中。


    public static void main(String[] args){

        String invalidPasswordRegex = "^([^0-9]*|[^A-Z]*|[^a-z]*|.{0,6}|[a-zA-z0-9]*)";
        String[] passwords = {"_conDitional4", "guerrilA", "Fies8&", "Salutation_007"};

        for(String password: passwords){
            if(!password.matches(invalidPasswordRegex)){
                System.out.println(password + " is a valid password match");
            }else{
                System.out.println(password + " is not a valid password match");
            }
        }

//      output: 
//              _conDitional4 is a valid password match
//              _guerrilA is not a valid password match
//              Fies8& is not a valid password match
//              Salutation_007 is a valid password match


    }

分配给invalidPasswordRegex 变量的regex是一个无效的密码regex,它不符合我们所有的要求。passwords 是一个潜在的密码输入数组。我们遍历这个数组,任何不符合无效密码重码的密码都是有效的;否则,给定的密码是无效的。

结论

虽然正则表达式在阅读和实现上有一定的难度,但我相信,只要彻底理解了正则表达式的组成单元,并对这些单元进行适当的拼接,其应用是无止境的,而且随着实践的深入会越来越好。