正则表达式从入门到以为精通再到每次使用查一遍

正则表达式(Regular Expression, 简称RegEx)是一些用来匹配和处理文本的字符串,它是用正则表达式语言创建的,这种语言的用途主要是为了检索和替换某些文本。

本文只是《正则表达式必知必会》和传说中的三十分钟学会正则表达式的一个小总结,因此,不打算从头开始介绍正则表达式,只记录一些知识点。

1. 一些元字符

元字符        意义

.        除了换行符之外的所有字符。当要表达.本身意思的时候,要使用转义符。

\d        任何一个数字字符(等价于[0-9])

\D       任何一个非数字字符(等价于[^0-9])

\w       任何一个字母数字字符或下划线字符(等价于[a-zA-Z0-9_]),可以理解为与变量命名规则类似

\W        任何一个非字母数字字符或非下划线字符(等价于[^a-zA-Z0-9_])

\s       任何一个空白字符(等价于[\f\n\r\t\v])

\S       任何一个非空白字符(等价于[^\f\n\r\t\v])

\b       任何一个单词开头或结尾的地方

\B       任何一个非单词开头或结尾的地方

^       放在正则表达式的最前面,表示以此开头

$       放在正则表达式的最后面,表示以些结尾

2. 匹配一组字符

使用[],可以匹配多个字符中的一个,如[NC]BA可以匹配NBA,也可以匹配CBA;

也可以利用字符集合区间匹配一个范围,通过-连接,如[0-9a-zA-Z] 或 class[A-D],都是可以的。字符区间的首、尾字符可以是ASCII字符表里的所有字符,但在实际工作中,最常用的字符区间还是数字字符区间和字母字符区间。

当然,字符区间也可以取非,通过^字符放在前面即可,如[^0-5],匹配6,7,8,9. 要注意的是^的效果将作用于给定字符集合里的所有字符或字符区间,而不是仅限于紧跟^字符后面的那一个字符或字符区间。因此^,如果放在区间的最前面,表示的就是取非,如果想要取其原来的意思,则必须使用转义符。如果不是在区间最前面,则可以不使用转义符

【举个栗子1】

如下示例-1,本意是想要匹配任何为a或^的字符(不要问为什么,就是有这样奇怪的要求)

1 String s = "a^";
2 Pattern pattern = Pattern.compile("[^a]*");
3 System.out.println(pattern.matcher(s).matches());

很明显这个表达式的意思是所有非a的字符,那么肯定是返回false了。

正确的写法应该是转义,或将^放在a后面。

1         String s = "a^";
2 //        String regex = "[a^]*";
3         String regex = "[\\^a]*";
4         Pattern pattern = Pattern.compile(regex);
5         System.out.println(pattern.matcher(s).matches());

表示范围的-,当前后没有字符的时候,不需要转义

3. 重复多次

字符      意义

?    表示0或只有1个,可以参考三目运算符

+    表示至少有1或更多

*    表示0或者有1或更多都可以

{n}   精确匹配n次

{n,}  至少匹配n次

{n,m} 至少匹配n次,至多匹配m次

However,这些都不是我要讲的重点。重点是,防止过度匹配

来看下面的例子

【举个栗子2】

假如有一个html格式文件,你想要找出里面的<p>标签,你可能会酱紫写:<p>.*</p>。然而,结果却是错的。

1         String s = "<p>fuck</p><p>you</p>";
2         String regex = "<p>.*</p>";
3         Pattern pattern = Pattern.compile(regex);
4         Matcher matcher = pattern.matcher(s);
5
6         while(matcher.find()) {
7             System.out.println(matcher.group());
8         }

也就是说,一次就把它匹配出来了,但你的想法是想要把这两个标签分别匹配出来,即匹配两次。这,就是过度匹配。

为什么会酱紫?因为*和+都是所谓的“贪婪型”元字符,它们在进行匹配时的行为模式是尽可能地从一段文本的开头匹配到结尾,而不是从这段文本的开头匹配到碰到第一个匹配时为止。

那么就要使用元字符的懒惰版本。

字符        意义

*?       匹配零个或1个或更多,但尽可能地少匹配

+?       匹配1个或更多,但尽可能地少匹配

??       匹配零个或1个,但尽可能地少匹配

{n,}?     匹配n个或更多,但尽可能地少匹配

{n,m}?    匹配至少n个,至多m个,但尽可能地少匹配

那么,上面的例子就只要把<p>.*</p>改为<p>.*?</p>就可以了。

4.子表达式或分组

把一个表达式划分为一系列子表达的目的是为了把那些子表达式当作一个独立元素来使用,可以认为把重复部分抽象出来。

分组的另一个主要作用是回溯引用(也叫向后引用)。回溯引用指的是模式的后半部分引用在前半部分定义的子表达式。

【举个栗子3】

假如有一个html文件,里面有从h1到h6的标签,你想要匹配正确的标签。注意是正确的标签,也就意味着,像<h1>fuckyou</h2>这样是不合法的。

待匹配的文本如下:

<h1>this is head</h1>

saysomething.

<h2>fuck you</h2>

<h2>actually its not valid</h3>

按照之前的学习,很容易写出如下代码:

1         String s = "<h1>this is head</h1>saysomething.<h2>fuck you</h2><h2>actually its not valid</h3>";
2         String regex = "<[hH][1-6]>.*?</[hH][1-6]>";
3         Pattern pattern = Pattern.compile(regex);
4         Matcher matcher = pattern.matcher(s);
5
6         while(matcher.find()) {
7             System.out.println(matcher.group());
8         }        

然而结果是把最后一个非法的标签也取出来了。为什么该标签是非法的?因为闭合标签必须跟它对应的标签名一致,即当你匹配了h1,后面只能继续匹配h1而不是其他,也即是说,后半部分的模式需要用到前半部分定义的子表达式。

将正则表达式改为 <([hH][1-6])>.*?</\1> 就好了,在Java中字符串记得对\进行转义。

回溯引用通常从1开始计数(\1,\2等),在许多实现里第0个匹配整个正则表达式。

那么问题就来了,对于嵌套分组,是怎么计数的?

总的来说,就是从一个大的分组往更小的分组不断地计数,个人感觉有点像前序遍历一棵树。

来看下面的例子。

【举个栗子4】

 1         String s = "ABCDEFHG";
 2         String regex = "(A)((B)(C))(D)((E(F(H)))(G))";
 3         Pattern pattern = Pattern.compile(regex);
 4         Matcher matcher = pattern.matcher(s);
 5
 6         while(matcher.find()) {
 7             System.out.println(matcher.group());
 8             for(int i = 1; i <= matcher.groupCount(); i++) {
 9                 System.out.println("group " + i + ": " + matcher.group(i));
10             }
11         }

这正则表达式问你怕了没?

运行结果如下,自行分析吧。

可以利用回溯引用来进行替换操作。

但是对于重复多次的分组,只能匹配到最近一次出现的分组,原因不明,请大神指教。下面的例子将结合这种情况进行替换操作。

【举个栗子5】

现在有个任务,需要将IP地址的第一部分替换掉。

你可以很容易地写出String regex = "((\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])"; 这样一个正则。然后根据刚才上面的分析,要进行替换,必然有以下的代码:

1         String s = "192.168.56.1";
2         String regex = "((\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])";
3
4         System.out.println(s.replaceAll(regex, "100.$3$5$7"));

然而结果是错的。

让我们来打印一下各个匹配到的分组都是什么。

从结果来看,前面三组重复的分组,只获取到了最后一个分组。

上面的例子需要改成如下方可正常运行。

1        String s = "192.168.56.1";
2         String regex = "((\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.)((\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.)((\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.)(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])";
3
4         System.out.println(s.replaceAll(regex, "100.$3$5$7"));

在替换时有些语言是支持将字母改为大写或小写的。Java不支持。

元字符          意义

\l           把下一个字符转换成小写(lowercase)

\u           把下一个字符转换成大写(uppercase)

\L          把\L和\E之间所有的字符都转换成小写

\U          把\U和\E之间所有的字符都转换成大写

\E          用来结束\L或\U转换

使用\u

结果:

使用\U

5.前后查找

最后一个知识点是关于前后查找的,该模式的作用是包含的匹配本身并不返回,而是用于确定正确的匹配位置,它并不是匹配结果的一部分。

想像一个场景,要把html文件里,某个标签之间的内容提取出来。或者在某个格式前面或后面把内容提取出来。

任何一个表达式都可以转换为一个向前查找表达式,只要给它加上一个?=前缀就可以了。同理向后查找,加上一个?<=前缀。

要注意的是,向前查找表达式,应放在模式的后面,而向向查找表达式,放在模式的前面。不能搞混了。

【举个栗子6】

假如有一批价格如下:

$5.00,$6.00,$7.00,

需要把价格提取出来计算,代码如下:

 1         String s = "$5.00,$6.00,$7.00,";
 2         String regex = "(?<=\\$).*?(?=,)";
 3
 4         Pattern pattern = Pattern.compile(regex);
 5         Matcher matcher = pattern.matcher(s);
 6
 7         double total = 0;
 8         while(matcher.find()) {
 9             System.out.println(matcher.group());
10             total += Double.parseDouble(matcher.group());
11         }
12
13         System.out.println("total:" + total);

运行结果如下:

最后啰嗦一下,向前查找(和向后查找)匹配本身其实是有返回结果的,只是这个结果的字节长度永远是0而已。所以有时也被称为零宽度(zero-width)匹配操作,或者零宽断言。

时间: 2024-10-18 10:15:37

正则表达式从入门到以为精通再到每次使用查一遍的相关文章

正则表达式(入门)

定锚点,去噪点,取数据 1.入门:正则字符 关于正则字符,很多文章都会讲到,足足有一篇文章才能描述清楚,我这里就不多说,对于我,平时,常用的有: . 匹配不包括换行的任意字符,在php的s修饰符下面可以匹配换行,如$pattern='#<div>(.*?)</div>#s';就可以匹配div内容有换行的数据. \s 空格.tab * 匹配零个或多个 + 匹配一个或多个,即至少一个 \ 转义 一个特殊字符前加\就表示转义,说明把它当普通字符用 [] 单字符取一个,比如[abc]会匹配

正则表达式快速入门,转载

正则表达式快速入门 首先简单介绍下正则表达式: 在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要.正则表达式就是用于描述这些规则的工具.换句话说,正则表达式就是记录文本规则的代码. 下面就看看正则表达式里乱七八糟的字符都是什么意思: 1.常用的元字符       代码                               说明                     . 匹配除换行符以外的任意字符 \w 匹配字母或数字或下划线或汉字 \s 匹配任意的空白符 \d

正则表达式简要入门(转载)

转载 http://luolei.org/regula-expression-simple-tutorial/ 第一次接触正则表达式是在今年四月的腾讯笔试,当时是一道选择题问如何判断输入的是否是 QQ 号码(即纯数字),当时是蒙了一个答案,菜鸟不会嘛 ╮(╯3╰)╭ .事后自己倒专门学习了正则表达式,还做了笔记,可是平时开发倒的确是用得少,最近倒也忘了,近来又是校招的季节,自己就重新整理一篇简要入门,分享给大家的同时,自己也复习复习. 资源推荐 <正则表达式30分钟入门教程> :请忽略这个「3

正则表达式简单入门

 正则表达式简单入门    正则表达式在平常编程中有着大量的应用,对于任何一个想学习编程的人来说,正则表达式是一个必须掌握的知识. 废话不多说,下面先对正则表达式做一个简单的入门介绍,在后续的文章中,将会进行详细的介绍.    一.元字符 元字符一共有12个:$ ( ) [ { ? + * . ^ \ | 元字符有特殊的含义,如果要使用其字面值,则必须对其进行转义. 如: \$  \*  \( 等等 二.控制字符或不可打印字符 \a  警报 \e  退出 \f  换页 \n  换行 \r 

正则表达式 简单入门

本文旨在介绍正则表达式最最基础的部分便于不知道的读者对正则表达式产生一个概念,科普一下(不一定能入门). 什么是正则表达式? 正则表达式,就是用来描述一个字符串结构的方法,和我们使用的通配符比较类似,但是又不一样,可以说更强大.正则表达式主要用来在字符串中搜索.替换.定位文本. 正则表达式可以干什么? 前面提到正则表达式能用来搜索和替换,我们就想到了文本编辑器.确实,目前很多文本编辑器都集成了正则表达式搜索匹配和替换的功能,比如常用的Notepad++ 有了正则表达式,我们可以很方便地实现一些功

正则表达式快速入门【转】

首先简单介绍下正则表达式: 在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要.正则表达式就是用于描述这些规则的工具.换句话说,正则表达式就是记录文本规则的代码. 下面就看看正则表达式里乱七八糟的字符都是什么意思: 1.常用的元字符       代码                               说明                     . 匹配除换行符以外的任意字符 \w 匹配字母或数字或下划线或汉字 \s 匹配任意的空白符 \d 匹配数字 \b 匹配

20天零基础Linux入门学习到精通视频直播

讲师介绍:老男孩IT教育-李导李老师多年Linux一线实战经验及Linux教学经验,擅长以图形表达讲解抽象概念,善于用 简单易懂的例子讲解重点难点,严肃不失幽默,著有<跟老男孩学Linux三剑客>一书. 本次课程知识点:帮你快速入门linux知识点,让你真正从小白到精通. 本次课程市场价值:599元 报名链接:https://ke.qq.com/course/202854 [课程大纲] 计算机基本组成-Linux服务器硬件基础-linux发展历史 计算机组成简介 服务器核心硬件组成.操作系统组

正则表达式快速入门

首先简单介绍下正则表达式: 在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要.正则表达式就是用于描述这些规则的工具.换句话说,正则表达式就是记录文本规则的代码. 下面就看看正则表达式里乱七八糟的字符都是什么意思: 1.常用的元字符       代码                               说明                     . 匹配除换行符以外的任意字符 \w 匹配字母或数字或下划线或汉字 \s 匹配任意的空白符 \d 匹配数字 \b 匹配

[原]正则表达式模式匹配入门

介绍 在实际项目中有个功能的实现需要解析一些特定模式的字符串.而在已有的代码库中,在已实现的部分功能中,都是使用检测特定的字符,使用这种方法的缺点是: 逻辑上很容易出错 很容易漏掉对一些边界条件的检查 代码复杂难以理解.维护 性能差 看到代码库中有一个cpp,整个cpp两千多行代码,有个方法里,光解析字符串的就有400余行!一个个字符对比过去,真是不堪入目.而且上面很多注释都已经过期,很多代码的书写风格也各不相同,基本可以判断是过了很多人手的. 在这种情况下,基本没办法还沿着这条老路走下去,自然