正则表达式及在Java中的应用

前言

正则表达式在我们日常开发或者是日常工作中十分常见,它能够很快地帮助我们定位到符合特定规则的文本内容,也常常用于判断某些文本是否合法(比如邮箱、手机号码的格式等等)。学好正则表达式可以让我们在工作中事半功倍,如果你对它还不熟悉的话,希望这篇文章能够给你参考。


一、正则表达式的语法

(一)普通字符

字母、数字、汉字、下划线、以及没有特殊定义的标点符号,都属于“普通字符”。正则表达式中的普通字符,在匹配到目标文本中与之相同的一个字符。
举个例子,我们可以用正则表达式为:aa的式子,来判断目标文本中是否存在aa字符串

image.png

(二)简单的转义字符
语法 含义
\n 换行符
\t 制表符
\ 表示\本身
\^,\$,\.,\(,\),\{,\},\?,\+,\*,|,\ [,\] 匹配这些字符本身

有关换行符和制表符(即按Tab键所生成的空格)的正则匹配,我们应该是用的最多的了。


image.png
(三)标准字符集合

标准字符集合是正则表达式提供给我们的可以用来匹配多种字符的表达式。

语法 含义
\d 任意一个数字,0-9中的任意一个
\w 任意一个字母或下划线,也就是AZ,az,0~9,_中的任意一个
\s 包括空格/制表符、换行符等空白字符中的其中任意一个
. 小数点可以匹配任意一个字符,如果要匹配包括"\n"在内的所有字符,一般用[\s\S]

标准字符集合相对于普通字符来说,具备更为强大的通配性,比如想匹配包含两个数字的话,只需要\d{2}就可以匹配出来了({2}为量词,相当于匹配两次,后面会讲到)

image.png

image.png

注意,标准字符集的使用要区分大小写,大写是相反的意思
也就是说,\d表示一个0-9的数字,\D表示的就是任何一个不是0-9的字符
image.png

(四)自定义字符集合
  • [ ]方括号匹配方式,可以匹配方括号中任意一个字符
    正则表达式允许我们自己根据需要来灵活地定义满足条件的字符集,比如说满足3,4,5中任何一个字符就匹配,我们可以用[345]进行定义。
举例 含义
[ab5@] 匹配"a"或"b"或"5"或"@"
[^ abc] 匹配"a","b","c"之外的任意一个字符
[f-k] 匹配"f"~"k"之间的任意一个字符
[^ A-F0-3] 匹配"A""F",03之间的任意一个字符
231 424 4424ds788
31e3422r4  rer3
13411r42eg
133ddgf3344
-- 匹配7-8或等于@的字符
[7-8@]
-- 匹配非数字字母字符
[^A-Za-z0-9]
  • 正则表达式的特殊符号,被包含到中括号中,则失去特殊含义,^,-除外。(比如说.就是只一个小数点,不再是代表任意一个字符)
  • 标准字符集合,除小数点外,如果被包含于中括号,自定义字符集合将包含该集合。比如:
    • [\d.-+]将匹配:数字,小数点,+-
(五)量词

量词是用于修饰匹配次数的特殊符号,比如说现在我们想文本中匹配满足5个数字的结果,我们可以写成\d\d\d\d\d,但是这样很繁琐,可以使用量词{5}表示匹配5次,也就是\d{5}
量词匹配还可以次数比较灵活,具体见下面

语法 含义
{n} 表达式重复n次
{m,n} 表达式最少重复m次,最多重复n次(也就是匹配次数的区间范围)
{m,} 表达式至少重复m次
? 匹配表达式0次或者1次,相当于{0,1}
+ 表达式至少重复1次,相当于{1,}
* 表达式重复0次或任意次,相当于{0,}
image.png

量词中还存在着贪婪和非贪婪两个模式

  • 贪婪模式为匹配字符越多越好,默认
  • 非贪婪模式为匹配字符越少越好(需要手动在量词后面加上一个?号)
    对贪婪模式的概念不清楚的话可以看一下下面的例子
    image.png

    image.png
(六)字符边界

字符边界和之前的匹配方式有着较大的不同,后者匹配的是字符,而字符边界匹配的是符合某种条件的位置

语法 含义
^ 从字符串开始的地方匹配
$ 从字符串结束的地方匹配
\b 匹配一个单词边界

简单的说,就是字符边界可以让我们根据位置来快速找到是否符合条件的数据

a123
b123
c321
d123a
-- 匹配以a开头的结果
^a
-- 匹配以字母开头的结果
^\w
-- 匹配以小写字母结尾的结果
[a-z]$

特别注意:字符边界使用的^和自定义字符集使用的^含义完全不同,后者的话是用于排他性匹配的。

(七)正则表达式的匹配模式(了解)
  • IGNORECASE 忽略大小写模式
    • 匹配时忽略大小写
    • 默认情况下,正则表达式区分大小写
  • SINGLELINE 单行模式
    • 整个文本看做一个字符串,只有一个开头,一个结尾
    • 是小数点可以匹配包含换行符\n在内的任意字符
  • MULTILINE 多行模式
    • 每行都是 一个字符串
    • 在指定了MULTILINE之后,如果需要仅匹配字符串开始和结束为止,可以使用\A\Z
(八)选择符和分组
表达式 作用
|分支结构 左右两边表达式之间“或”关系,匹配左边或者右边
( )捕获组 (1)在被修饰匹配次数的时候,括号中的表达式可以作为整体被修饰(2)取匹配结果的时候,括号中的表达式匹配到的内容可以被单独得到(3)每一对括号会分配一个编号,使用()的捕获根据左括号的顺序从1开始自动编号。捕获元素编号为零的第一个捕获是由整个正则表达式模式匹配的文本
(?:Expression)非捕获组 一些表达式中,不得不使用(),但又不需要保存()中的子表达式匹配的内容,这时可以用非捕获组来低效使用()带来的副作用
举例说明一下:
1. 分支结构
a123
b123
c321
d123a
-- [a-c] | [1-3]{3} 表示满足a-c或者满足三位1-3的数字就可以匹配。所以一共有8组匹配结果
a、123、b、123、c、321、123、a
2、捕获组,就是捕获不同的括号匹配到的内容。我们可以用它来实现一些复杂的正则匹配
aa123
bb123
cb321
d123aabbb
-- 匹配aa、bb、cc的所有文本
([a-c])\1
解释一下, [a-c]表示匹配到a到c的所有字母, \1捕获到第一个捕获组的内容
当第一个捕获组捕获到a的时候,\1表示的就是a,以此类推,就可以捕获到所有复数的字母了。
如果有三个相同字母的情况也很简单,([A-Za-z])\1\1
3、反捕获组
利用捕获组进行反向引用的本质,是将捕获组捕获到的内容存在内存中,方便反向引用的时候使用,但是当捕获组太多的时候会占用太多的内存,这个时候我们就可以使用反捕获组进行标识啦
  • 反向引用(\nnn
    • 每一对()会分配一个编号,使用()的捕获根据左括号的顺序从1开始自动编号
    • 通过反向引用,可以对分组已捕获的字符串进行引用
(九)预搜索(零宽断言)
  • 只进行子表达式的匹配,匹配内容不计入最终的匹配结果,是零宽度
  • 这个位置应该符合某个条件。判断当前位置的前后字符,是否符合指定的条件,但不匹配前后的字符。是对位置的匹配
  • 正则表达式匹配过程中,如果子表达式匹配到的是字符内容,而非位置,并被保存到最终的匹配结果中,那么就认为这个子表达式是占有字符的;如果子表达式匹配的仅仅是位置,或者匹配的内容并不保存到最终的匹配结果中,那么就认为这个子表达式是零宽度的。占有字符还是零宽度,是针对匹配的内容是否保存到最终的匹配结果而言的。
表达式 作用
(?=exp) 断言自身出现的位置后面可以匹配表达式exp
(?<=exp) 断言自身出现的位置前面可以匹配表达式exp
(?!exp) 断言自身出现的位置后面不可以匹配表达式exp
(?<!exp) 断言自身出现的位置前面不可以匹配表达式exp

乍看一下,字符边界和预搜索很像,二者都能根据字符的位置去匹配文本
但两者实际上还是有所不同的,最主要的区别在于是否零宽

比如我们现在想在下面的文本中匹配开头两位为a-c的字母,后面是1-3的数字,使用字符边界可以这样写^([a-c]{2})[1-3]+

image.png

而使用零宽断言的话,写法和结果是这样的
image.png

还有一定要注意的,无论使用零宽断言还是字符边界,前后匹配的字符要和正则的一致。(也就是说,如果是匹配前面的字符,表达式要写在前面,否则写后面

二、在Java程序中使用正则表达式

  • 类Pattern
    • 正则表达式的编译表现形式
    • Pattern p = Pattern.compile(r,int); //建立并启用正则表达式
  • 类Matcher
    • 通过解释Pattern对character sequence执行匹配操作的引擎
    • Matcher m = p.matcher(str); // 匹配字符串
1、Matches 判断字符串是否满足正则要求
package com.xiaoming;

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

public class PatternTest {

    private static final String STR1="aabbcc123aabbcc1234@12345.com";
    private static final String STR2="aabbcc123aabbcc12";

    public void matches(String patternRule){
        //定义表达式对象
        Pattern pattern = Pattern.compile(patternRule);
        // 创建Matcher匹配对象
        Matcher m1 = pattern.matcher(STR1);
        Matcher m2 = pattern.matcher(STR2);
        boolean result1 = m1.matches();
        boolean result2 = m2.matches();
        System.out.println("STR1中对正则"+patternRule+"的匹配结果是:"+result1);
         //STR1中对正则[a-c1-3]+的匹配结果是:false
        System.out.println("STR2中对正则"+patternRule+"的匹配结果是:"+result2);
           //STR2中对正则[a-c1-3]+的匹配结果是:true
    }

    public static void main(String[] args) {
        PatternTest patternTest = new PatternTest();
        // 判断文本中是否满足正则的要求
        patternTest.matches("[a-c1-3]+");
    }

}

所以,matches方法很适合用来直接校验某些文本是否满足正则格式,可以用来校验邮箱和手机格式是否正确

2、Find和Group 查找和匹配符合正则要求的下一个序列
public static void main(String[] args) {
        PatternTest patternTest = new PatternTest();
        // 判断文本中是否满足正则的要求
        //patternTest.matches("[a-c1-3]+");
        List<String> result =  patternTest.findAndGroup("[\\d]+");
        System.out.println(result);
       // 可以看到,我们把每个符合正则的子序列截取了出来[123, 1234, 12345] 
    }

    private List<String> findAndGroup(String patternRule) {
        List list = new ArrayList<String>();
        Pattern pattern = Pattern.compile(patternRule);
        Matcher matcher = pattern.matcher(STR1);
        while (matcher.find()){
            String str1 = matcher.group();
            list.add(str1);
        }
        return list;
    }
3、ReplaceAll 替换掉所有和正则匹配的结果
    private static final String STR1="aabbcc123aabbcc1234@12345.com";
    public void replaceAll(String patternRule){
        String result = STR2.replaceAll(patternRule, "!");
        System.out.println(result);
        //  aabbcc!!!aabbcc!!
    }
public static void main(String[] args) {
        PatternTest patternTest = new PatternTest();
        patternTest.replaceAll("[\\d]");
    }

比较常见的方式是,使用这个方法来去除用户填写数据中存在的空格。str.replaceAll("\\s","");

4、split 根据正则将文本进行分割
private static final String STR1="aabbcc123aabbcc1234@12345.com";

public static void main(String[] args) {
        PatternTest patternTest = new PatternTest();
        patternTest.split("\\d+");
    }
    public void split(String patternRule){
        List<String> list = Arrays.asList(STR1.split(patternRule));
        // 将文本按照正则进行截断
        System.out.println(list);
      // 结果是 [aabbcc, aabbcc, @, .com]
    }

我们常常用某个符号拼装一些数据后,传到某些方法中再集中做数据拆分。就比如将用户的多个爱好用$拼接起来后,再通过split方法将一个个爱好单独拎出来。

三、补充一些实用的正则案例

案例一 给指定格式的文本前后加固定文本内容

对于下面的文本,我们希望在每行数字的前后都加上单引号,且每行数字的最后需要加一个逗号。

123456
456789
123456
步骤一:统一处理数字后面的新增字符串

通过观察,我们可以发现我们可以通过2个规律来捕捉到对应的文本,①换行符 ②每行数字都是6位数。因为这个场景比较简单,选择换行符就能满足我们的需要,我们将所有\r\n的地方替换为',\r\n就能处理完每行数字后面的字符串内容。

image.png

步骤二:统一处理数字前面新增的字符串

使用零宽断言来定位位置,在匹配到的位置上面插入'即可

image.png

PS:选择\r\n替换为',\r\n,其实也是一个不错的选择,只需要首位自己手动补一下符号就行

当然了,这里我们还可以使用反捕获组来更快的解决这个问题,只需要根据每行数字写一下正则来抓取,再根据反捕获组\1直接调整每行的内容,更优雅。

image.png

案例二:过滤出指定格式的字符串内容

假如说给定以下文本,我们希望将USER_ID:XXXXXX的所有文本提取出来,可以怎么实现呢?

Jan 8, 2023 @ 23:50:59.054  WARN  [3212023] - [88db89e4-209c-48b1-b6b1-a722e13b1b88] - {auth.XXXXUserExitImpl.authenticate:100} - [鐧诲綍鐧诲綍] - [USER_ID:608899] -> SSO 鐧诲綍鐧诲綍鐧诲綍?[10.19.140.165] 鐧诲綍鐧诲綍鐧诲綍?[Sun Jan 08 23:50:58 CST 2023] 鐧诲綍:[0.151]
Jan 8, 2023 @ 23:41:24.213  WARN  [4493547] - [d9322ff3-e0d1-4f00-9927-c11f22240eb0] - {auth.XXXXUserExitImpl.authenticate:100} - [鐧诲綍鐧诲綍] - [USER_ID:788890] -> SSO 鐧诲綍鐧诲綍鐧诲綍?[10.127.14.45] 鐧诲綍鐧诲綍鐧诲綍?[Sun Jan 08 23:41:23 CST 2023] 鐧诲綍:[0.066]

从文本的内容来看,我们很容易就能提取出USER_ID:\d{6}的正则表达式,那么接下来我们只需要对将该文本前后的内容删除掉就能得到结果。可以使用下面两条正则来分别匹配前后的文本,用空白字符串替换即可。

(?<=USER_ID:\d{6}).* 
.+(?=USER_ID:\d{6})
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 前段时间做聊天消息的解析,涉及到要在群消息中查找@的人,需要把@后面的用户昵称解析出来,首先想到的当然就是用正则来...
    waiwaaa阅读 1,119评论 0 2
  • 正则表达式(Java版整理) 基础 元字符 重复 字符类 想查找数字,字母或数字,空白是很简单的,因为已经有了对应...
    NowBurn阅读 1,033评论 0 1
  • 元字符 代码说明.匹配除换行符以外的任意字符\w匹配字母或数字或下划线或汉字\s匹配任意的空白符\d匹配数字^匹配...
    Mr_Fly阅读 620评论 0 0
  • 参考文章:正则表达式30分钟入门教程 语法:正则表达式语法手册 正则表达式在Java和其他语法中的区别 在其他语言...
    小石头呢阅读 2,307评论 0 1
  • 正则表达式简介 为什么需要正则表达式 文本的复杂处理 正则表达式的优势和用途 一种强大而灵活的文本处理工具 大部分...
    努力生活的西鱼阅读 1,213评论 0 0