【正则表达式】Java

以下内容均为个人学习笔记。

01

正则的意思是正规、规则。正则表达式的英文名是 Regular Expression,可以直译为描述某种规则的表达式,一般缩写为 regex。


02

判断一个字符串是不是有效的电话号码?

public static boolean isValidPhoneNumber(String number) {
    return number.matches("\\d{11}");
}

03

正则表达式的精确匹配。一个普通的字符串,比如 abc,它如果用来做正则表达式匹配的话,只能匹配自己。也就是说它只能匹配字符串 abc,不能匹配 abAbc 等其他任何字符串。

System.out.println("abc".matches("abc")); // 输出为 true
System.out.println("ab".matches("abc")); // 输出为 false
System.out.println("Abc".matches("abc")); // 输出为 false

如果需要匹配的字符串含有特殊字符,那就需要用 \ 转义。比如 a&b,在用正则表达式匹配时,需要使用 a\&b,又由于在 Java 字符串中,\ 也是特殊字符,它也需要转义,所以 a\&b 对应的 Java 字符串是 a\\&b,它是用来匹配 a&b 的。

System.out.println("a&b".matches("a\\&b")); // 输出为 true

这两个反斜杠的意义竟然还不一样:一个是正则的转义,一个是 Java 字符串的转义

\d 在正则表达式中表示匹配任意数字,d 是 digital 的简写。比如 00\d 就可以匹配 000007008 等等。

  • \d 后面打上花括号 {}{n} 表示匹配 n 次。\d{10000} 就表示匹配 10000 个数字。
  • 如果要匹配 n ~ m 次,用 {n,m} 即可,如果要匹配至少 n 次,用 {n,} 即可。需要注意 , 后不能有空格。
  • 如果要匹配最多 m 次, 直接用 {0,m}, 没有 {,m} 这样的写法。
System.out.println("1".matches("\\d{1,2}")); // 输出为 true
System.out.println("12".matches("\\d{1,2}")); // 输出为 true
System.out.println("123".matches("\\d{1,2}")); // 输出为 false

System.out.println("123".matches("\\d{2,}")); // 输出为 true

04

正则的基础规则中,除了 \d,还有 \w\s,w 是 word 的简写,表示匹配一个常用字符,包括字母、数字、下划线。s 是 space 的简写,表示匹配一个空格,包括三种:

  • 空格键打出来的空格
  • Tab 键打出来的空格 \t
  • 回车键打出来的空格 \n
System.out.println("LeetCode_666".matches("\\w{12}")); // 输出为 true
System.out.println("\t \n".matches("\\s{3}")); // 输出为 true
System.out.println("Leet\tCode 666".matches("\\w{4}\\s\\w{4}\\s\\d{3}")); // 输出为 true

05

将字母换成大写,就表示相反的意思。用 \d 你可以匹配一个数字,\D 则表示匹配一个非数字。

System.out.println("a".matches("\\d")); // 输出为 false
System.out.println("1".matches("\\d")); // 输出为 true

System.out.println("a".matches("\\D")); // 输出为 true
System.out.println("1".matches("\\D")); // 输出为 false

类似地,\W 可以匹配 \w 不能匹配的字符,\S 可以匹配 \s 不能匹配的字符。


06

有时候,我们对某些位置的字符没有要求,仅需要占个位置即可。这时候我们就可以用 . 字符。

System.out.println("a0b".matches("a.b")); // 输出为 true
System.out.println("a_b".matches("a.b")); // 输出为 true
System.out.println("a b".matches("a.b")); // 输出为 true

. 可以匹配任意字符。

有时候,我们对匹配的次数没有要求,匹配任意次均可,这时,我们就可以用 * 字符。

System.out.println("1".matches("\\d*")); // 输出为 true
System.out.println("123".matches("\\d*")); // 输出为 true
System.out.println("".matches("\\d*")); // 输出为 true

* 是指可以匹配任意次,包括 0 次。也就是说,* 等价于 {0,}
+ 匹配,+ 表示 至少匹配一次。它等价于 {1,}

System.out.println("1".matches("\\d+")); // 输出为 true
System.out.println("123".matches("\\d+")); // 输出为 true
System.out.println("".matches("\\d+")); // 输出为 false

还有一种场景,如果某个字符要么匹配 0 次,要么匹配 1 次,我们就可以用 ? 匹配。它等价于 {0,1}

System.out.println("".matches("\\d?")); // 输出为 true
System.out.println("1".matches("\\d?")); // 输出为 true
System.out.println("123".matches("\\d?")); // 输出为 false
  • . 匹配任意字符
  • * 匹配任意次,包括 0 次
  • + 号匹配至少 1 次
  • ? 匹配 0 次或 1 次

07

[] 用于匹配指定范围内的字符,比如 [123456789] 可以匹配 1~9。
这里还有一个语法糖,[123456789] 写起来太麻烦,可以写作 [1-9]
比如 [a-g] 表示 [abcdefg][U-Z] 表示 [UVWXYZ]

但如果既可以是数字 1~9,又可以是字母 a~g,还可以是字母 U~Z,还是得把所有范围列出来。不必,你还可以这么写:[1-9a-gU-Z]

System.out.println("1".matches("[1-9a-gU-Z]")); // 输出为 true
System.out.println("b".matches("[1-9a-gU-Z]")); // 输出为 true
System.out.println("X".matches("[1-9a-gU-Z]")); // 输出为 true
System.out.println("A".matches("[1-9a-gU-Z]")); // 输出为 false



如果是 0 ~ 1,8 ~ 9 可以这样组合吗?
[0189]更简洁。
[0-18-9] 正是你想要的。由于正则一次只匹配一个字符,所以这样写并不会有歧义,也就是说计算机不会把这种写法误解成要匹配 0~18 之类的。

System.out.println("1".matches("[0-18-9]")); // 输出为 true
System.out.println("5".matches("[0-18-9]")); // 输出为 false

还有一种写法可以实现这一点,那就是用 运算符,正则的 运算符是 |[0189] 也可以写作 0|1|8|9

System.out.println("1".matches("0|1|8|9")); // 输出为 true
System.out.println("5".matches("0|1|8|9")); // 输出为 false

可以实现更多的功能,它并不局限于单个字符。

System.out.println("abc".matches("abc|ABC")); // 输出为 true
System.out.println("ABC".matches("abc|ABC")); // 输出为 true
System.out.println("123".matches("abc|ABC")); // 输出为 false



如果我想排除某些字符呢?比如这个位置不能是 [123]。我记得你之前说正则王国以大写表示取反,[] 要怎么大写呢?
[] 可没有大写之说,[] 取反的方式是:[^],比如不能是 [123] 的表示方法为 [^123] 或者 [^1-3]


08

Name:Aurora        Age:18
其中还夹杂着一些无关紧要的数据
Name:Bob            Age:20
错误的数据有着各种各样错误的格式
Name:Cassin        Age:22
...
System.out.println("Name:Aurora   Age:18".matches("Name:\\w+\\s*Age:\\d{1,3}")); // 输出为 true
System.out.println("其中还夹杂着一些无关紧要的数据".matches("Name:\\w+\\s*Age:\\d{1,3}")); // 输出为 false
System.out.println("Name:Bob      Age:20".matches("Name:\\w+\\s*Age:\\d{1,3}")); // 输出为 true
System.out.println("错误的数据有着各种各样错误的格式".matches("Name:\\w+\\s*Age:\\d{1,3}")); // 输出为 false
System.out.println("Name:Cassin   Age:22".matches("Name:\\w+\\s*Age:\\d{1,3}")); // 输出为 true

一般来说,下一步你要做的就是取出这些表达式中的姓名和年龄,以便把它们存到数据库中。

Pattern pattern = Pattern.compile("Name:(\\w+)\\s*Age:(\\d{1,3})");
Matcher matcher = pattern.matcher("Name:Aurora   Age:18");
if(matcher.matches()) {
    String group1 = matcher.group(1);
    String group2 = matcher.group(2);
    System.out.println(group1);   // 输出为 Aurora
    System.out.println(group2);   // 输出为 18
}

只要用 () 将需要取值的地方括起来,传给 Pattern 对象,再用 Pattern 对象匹配后获得的 Matcher 对象来取值就行了。每个匹配的值将会按照顺序保存在 Matcher 对象的 group 中。

你可以看到我用 ()\\w+\\d{1,3} 分别括起来了,判断 Pattern 对象与字符串是否匹配的方法是 Matcher.matches(),如果匹配成功,这个函数将返回 true,如果匹配失败,则返回 false。

为什么 group 是从下标 1 开始取值的,计算机不都从 0 开始数吗?
这是因为 group(0) 被用来保存整个匹配的字符串了。

System.out.println(matcher.group(0));   // 输出为 Name:Aurora   Age:18



阅读源码之后,你可以发现,每次调用 String.matches 函数,都会新建出一个 Pattern 对象。所以如果要用同一个正则表达式多次匹配字符串的话,最佳的做法不是直接调用 String.matches 方法,而应该先用正则表达式新建一个 Pattern 对象,然后反复使用,以提高程序运行效率。

// 错误的做法,每次都会新建一个 Pattern,效率低
boolean result1 = "Name:Aurora   Age:18".matches("Name:(\\w+)\\s*Age:(\\d{1,3})"); 
boolean result2 = "Name:Bob      Age:20".matches("Name:(\\w+)\\s*Age:(\\d{1,3})");
boolean result3 = "Name:Cassin   Age:22".matches("Name:(\\w+)\\s*Age:(\\d{1,3})");
                                                 
// 正确的做法,复用同一个 Pattern,效率高
Pattern pattern = Pattern.compile("Name:(\\w+)\\s*Age:(\\d{1,3})");
boolean result4 = pattern.matcher("Name:Aurora    Age:18").matches();
boolean result5 = pattern.matcher("Name:Bob      Age:20").matches();
boolean result6 = pattern.matcher("Name:Cassin   Age:22").matches();

09

二分,回溯,递归,分治
搜索;查找;旋转;遍历
数论 图论 逻辑 概率

split 函数的源码,这个函数传入的参数实际上是一个正则表达式。
用正则表达式模糊匹配,只要能匹配成功,就以其分割。

System.out.println(Arrays.toString("二分,回溯,递归,分治".split("[,;\\s]+")));
System.out.println(Arrays.toString("搜索;查找;旋转;遍历".split("[,;\\s]+")));
System.out.println(Arrays.toString("数论 图论 逻辑 概率".split("[,;\\s]+")));

输出为:

[二分, 回溯, 递归, 分治]
[搜索, 查找, 旋转, 遍历]
[数论, 图论, 逻辑, 概率]



可以用正则表达式模糊匹配,将符合规则的字符串全部替换掉。比如就现在这个例子,我们可以把用户输入的所有数据统一规范为使用 ; 分隔,那我们就可以这样写。

System.out.println("二分,回溯,递归,分治".replaceAll("[,;\\s]+", ";"));
System.out.println("搜索;查找;旋转;遍历".replaceAll("[,;\\s]+", ";"));
System.out.println("数论 图论 逻辑 概率".replaceAll("[,;\\s]+", ";"));

输出为:

二分;回溯;递归;分治
搜索;查找;旋转;遍历
数论;图论;逻辑;概率



还不止这一点,在 replaceAll 的第二个参数中,我们可以通过 $1$2,...来反向引用匹配到的子串。只要将需要引用的部分用 () 括起来就可以了。

System.out.println("二分,回溯,递归,分治".replaceAll("([,;\\s]+)", "---$1---"));
System.out.println("搜索;查找;旋转;遍历".replaceAll("([,;\\s]+)", "---$1---"));
System.out.println("数论 图论 逻辑 概率".replaceAll("([,;\\s]+)", "---$1---"));

输出为:

二分---,---回溯---,---递归---,---分治
搜索---;---查找---;---旋转---;---遍历
数论--- ---图论--- ---逻辑--- ---概率

有时候我们不需要替换,只需要将正则匹配出来的部分添加一些前缀或后缀,就可以用这种方式!


10

给你一些字符串,统计其末尾 e 的个数

LeetCode
LeetCodeeee
LeetCodeee
Pattern pattern = Pattern.compile("(\\w+)(e*)");
Matcher matcher = pattern.matcher("LeetCode");
if (matcher.matches()) {
    String group1 = matcher.group(1);
    String group2 = matcher.group(2);
    System.out.println("group1 = " + group1 + ", length = " + group1.length());
    System.out.println("group2 = " + group2 + ", length = " + group2.length());
}

输出为:

group1 = LeetCode, length = 8
group2 = , length = 0

我期望的结果是 group1 等于 LeetCod,group2 等于 e 才对啊!

这是因为 e 仍然属于 \w 能匹配的范畴,正则表达式默认会尽可能多地向后匹配,我们王国将其称之为 贪婪匹配

贪婪匹配和贪心算法原理是一致的。与之对应的匹配方式叫做 非贪婪匹配,非贪婪匹配会在能匹配目标字符串的前提下,尽可能少的向后匹配。
在需要非贪婪匹配的正则表达式后面加个 ? 即可表示非贪婪匹配。

Pattern pattern = Pattern.compile("(\\w+?)(e*)");
Matcher matcher = pattern.matcher("LeetCode");
if (matcher.matches()) {
    String group1 = matcher.group(1);
    String group2 = matcher.group(2);
    System.out.println("group1 = " + group1 + ", length = " + group1.length());
    System.out.println("group2 = " + group2 + ", length = " + group2.length());
}

输出为:

group1 = LeetCod, length = 7
group2 = e, length = 1

这里也用的是 ?,我记得之前 ? 表示的是匹配 0 次或者 1 次,两个符号不会混淆吗?

不会混淆的,你仔细想一想就能明白了,如果只有一个字符,那就不存在贪婪不贪婪的问题,如果匹配多次,那么表示非贪婪匹配的 ? 前面必有一个标志匹配次数的符号。所以不会出现混淆。

非贪婪匹配的定义是在能匹配目标字符串的前提下,尽可能少的向后匹配


11

我们王国有一个人口吃,请你帮忙矫正他。
他今天说:肚...子。。好饿........,....早知道.....当.....初...。。。多.....刷.....点。。。力.....扣了.........!
String message = "肚...子。。好饿........,....早知道.....当.....初...。。。多.....刷.....点。。。力.....扣了.........!";
System.out.println(message.replaceAll("[.。\\s]+", ""));

输出为:

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

推荐阅读更多精彩内容

  • 资源来自菜鸟教程:http://www.runoob.com/regexp/regexp-syntax.html ...
    贼厉害阅读 10,465评论 0 6
  • re模块手册 本模块提供了和Perl里的正则表达式类似的功能,不关是正则表达式本身还是被搜索的字符串,都可以...
    喜欢吃栗子阅读 4,010评论 0 13
  • \ :将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,"n"匹配字符"n"。"\n"匹配换行符。序列...
    小沙鹰168阅读 544评论 0 1
  • 正则表达式到底是什么东西?字符是计算机软件处理文字时最基本的单位,可能是字母,数字,标点符号,空格,换行符,汉字等...
    狮子挽歌阅读 2,148评论 0 9
  • 忘了从哪收集的资料了,放这儿,以备不时之需。 只能输入数字:"^[0-9]*$"。 只能输入n位的数字:"^\d{...
    study_monkey阅读 1,405评论 0 7