正则表达式学习笔记

正则表达式概念

正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为"元字符")组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。

正则表达式一些基础的知识:
http://www.runoob.com/regexp/regexp-tutorial.html
正则表达式在线工具:
https://c.runoob.com/front-end/854

对于刚接触的正则表达式的人,有些教程和文章对正则表达式的解释是很抽象和难以理解的,这就需要我们多写代码去理解和体会正则则表达式。先对正则表达式体系和概念整体有个理解,然后在回头细看和研究它。下面的内容对正则表达式中的重要部分进行一下梳理。仅供参考。

什么是捕获组

捕获组:正则表达式中的子表达式,这些子表达式用()括起,每一个()表示一个捕获组。会把()内的表捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用。当然,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部。
捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用。当然,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部。

捕获组有两种形式,一种是普通捕获组,另一种是命名捕获组,通常所说的捕获组指的是普通捕获组。语法如下:
普通捕获组:(Expression)
命名捕获组:(?<name>Expression)
我们可以看到()括号内的表达式就是一个捕获组。本篇文章只对普通捕获组做介绍。

普通捕获组命名规则

普通捕获组以"("括号出现的顺序从数字1开始进行进行递增编号。
正则表达式:(\d{4})-(\d{2}-(\d\d))匹配一个格式为yyyy-MM-dd格式的日期。捕获组的编号下:

以上正则表达式匹配1990-11-22字符串如下:
捕获组编号表达式匹配内容
0(\d{4})-(\d{2}-(\d\d))1990-11-22
1(\d{4})1990
2(\d{2}-(\d\d))11-22
3(\d\d)22









注意:正则表达式匹配的内容(以下称为输入序列)也有个编号,这个编号是0。

通过事例理解捕获组

这里说明一下:后续给出的所有理解正则表达式的事例都是基于matcherContent()方法。
/**
	 * 输入序列与模式匹配的内容
	 * @param input	输入序列
	 * @param pattern 模式
	 */
	public static void matcherContent(String input, String pattern) {

		log.info("input:" + input);
		log.info("pattern:" + pattern);

		Pattern p = Pattern.compile(pattern);
		Matcher m = p.matcher(input);

		int c = 1;
		while (m.find()) {
			log.info("第" + (c++) + "个匹配模式的子序列.");
			log.info("group(0):[" + m.group(0) + "] start:[" + m.start() + "] end[" + m.end() + "]");
			int groupCount = m.groupCount();
			for (int i = 1; i <= groupCount; i++) {
				log.info("group (" + i + "):[" + m.group(i) + "] start:[" + m.start() + "] end[" + m.end() + "]");
			}
		}
		log.info("--------------END----------------");
	}

事例1

查找输入序列内的yyyy-MM-dd格式的日期。
matcherContent("hi,我是1990-11-22出生的,你好像是1991-03-12吧!","(\\d{4})-(\\d{2})-(\\d\\d)");
输出结果:
2017-08-22 19:24:33 - INFO - (RegexTest2.java:41) - input:hi,我是1990-11-22出生的,你好像是1991-03-12吧!
2017-08-22 19:24:33 - INFO - (RegexTest2.java:42) - pattern:(\d{4})-(\d{2})-(\d\d)
2017-08-22 19:24:33 - INFO - (RegexTest2.java:49) - 第1个匹配模式的子序列.
2017-08-22 19:24:33 - INFO - (RegexTest2.java:50) - group(0):[1990-11-22] start:[5] end[15]
2017-08-22 19:24:33 - INFO - (RegexTest2.java:53) - group (1):[1990]
2017-08-22 19:24:33 - INFO - (RegexTest2.java:53) - group (2):[11]
2017-08-22 19:24:33 - INFO - (RegexTest2.java:53) - group (3):[22]
2017-08-22 19:24:33 - INFO - (RegexTest2.java:49) - 第2个匹配模式的子序列.
2017-08-22 19:24:33 - INFO - (RegexTest2.java:50) - group(0):[1991-03-12] start:[23] end[33]
2017-08-22 19:24:33 - INFO - (RegexTest2.java:53) - group (1):[1991]
2017-08-22 19:24:33 - INFO - (RegexTest2.java:53) - group (2):[03]
2017-08-22 19:24:33 - INFO - (RegexTest2.java:53) - group (3):[12]
2017-08-22 19:24:33 - INFO - (RegexTest2.java:56) - --------------END----------------
从输出结果我们可以看到:模式在输入序列中查找到两个子序列(一个输入序列中的一部分叫做子序列)。第一个子序列为“1990-11-22”,可以通过group(0)或group()获取,由于我们使用了捕获组,三个捕获组把子序列有分为了三个组,编号从1到3,编号为1的捕获组匹配到yyyy格式内容,编号为2的捕获组匹配到MM格式的内容,编号为3的捕获组匹配到dd格式的内容。第二个子序列"1991-03-12"一次类图。

非捕获组

有的时候我们将表达式放在()内是为了作为一个整体或让表达式运算优先级高一下,不想作为不捕获组匹配的内容在内存中存储。
在()括号内的表达式前加上?:、?!、?=都是代办非捕获组,匹配内容不以编号形式存储在内存中,他们有一些区别,下面分别介绍。

?:

?:的语法为(?:pattern)。它是一个非捕获匹配。
?:事例
//匹配出JDK1.4和JDK1.5,1.4和1.5不作为捕获组
matcherContent("JDK1.4、JDK1.5、JDK1.6、JDK1.7","JDK(?:1.4|1.5)");
输出结果:
2017-08-22 19:20:25 - INFO - (RegexTest2.java:41) - input:JDK1.4、JDK1.5、JDK1.6、JDK1.7
2017-08-22 19:20:25 - INFO - (RegexTest2.java:42) - pattern:JDK(?:1.4|1.5)
2017-08-22 19:20:25 - INFO - (RegexTest2.java:49) - 第1个匹配模式的子序列.
2017-08-22 19:20:25 - INFO - (RegexTest2.java:50) - group(0):[JDK1.4] start:[0] end[6]
2017-08-22 19:20:25 - INFO - (RegexTest2.java:49) - 第2个匹配模式的子序列.
2017-08-22 19:20:25 - INFO - (RegexTest2.java:50) - group(0):[JDK1.5] start:[7] end[13]
从输出结果可以看到,并没有输出"group (1):[1.4]"和"group (1):[1.5]",说明没有作为一个捕获组将匹配的内容存储。

?=

?=语法为(?=pattern).它的专业名称叫做:执行正向预测先行搜索的子表达式。
?=事例
		//匹配出JDK1.4和JDK1.5中的JDK。也就是说匹配出JDK紧跟着1.4和1.5的JDK
		matcherContent("JDK1.4、JDK1.5、JDK1.6、JDK1.7","JDK(?=1.4|1.5)");
输出结果:
2017-08-22 19:22:41 - INFO - (RegexTest2.java:41) - input:JDK1.4、JDK1.5、JDK1.6、JDK1.7
2017-08-22 19:22:41 - INFO - (RegexTest2.java:42) - pattern:JDK(?=1.4|1.5)
2017-08-22 19:22:41 - INFO - (RegexTest2.java:49) - 第1个匹配模式的子序列.
2017-08-22 19:22:41 - INFO - (RegexTest2.java:50) - group(0):[JDK] start:[0] end[3]
2017-08-22 19:22:41 - INFO - (RegexTest2.java:49) - 第2个匹配模式的子序列.
2017-08-22 19:22:41 - INFO - (RegexTest2.java:50) - group(0):[JDK] start:[7] end[10]
2017-08-22 19:22:41 - INFO - (RegexTest2.java:56) - --------------END----------------
从输入结果可以看到:
1,没有有输出"(?=1.4|1.5)"匹配到的1.4和1.5。这相当于于:select name form user where name='abc' and id='100',我的查询条件是name和id,但是select部分只显示name。也就是说?:后的表达式匹配的内容不包含在匹配的内容中。
2,下一匹配的搜索紧随上一匹配之后,而不是从(?=表达式)匹配的内容后开始。上面的事例首先匹配到JDK1.4,然后,?=后的表达式去做匹配内容的操作,但匹配到的内容不作为序列的一部分显示。下一次搜索从1.4开始,而不是从1.4后的“、”开始搜索。也就是说(?!表达式)匹配的内容宽度为0。

?!

?!语法为(?!pattern)。它的专业名称叫做:执行反向预测先行搜索的子表达式。
?!事例
		//匹配出不包含JDK1.4和JDK1.5中的JDK。
		matcherContent("JDK1.4、JDK1.5、JDK1.6、JDK1.7","JDK(?!1.4|1.5)");
//匹配出不包含JDK1.4和JDK1.5中的JDK。也就说匹配出JDK不紧跟着1.4和1.5的JDK
matcherContent("JDK1.4、JDK1.5、JDK1.6、JDK1.7","JDK(?!1.4|1.5)");
?!和?=两个特点是一样的,只不过?!是反向的取值,不匹配表达式的内容,?=是正向取值,匹配表达式的内容。

综合事例
字符串"a1a2a3",将数字2前面的a替换成A。
	public static void test12(){
		String input = "a1a2a3";
		String pattern="a(?=2)+";
		String replacement = "A";
		
		Matcher m = Pattern.compile(pattern).matcher(input);
		log.info(m.replaceAll(replacement));
	}
输出结果:
2017-08-24 09:40:51 - INFO - (RegexTest1.java:118) - a1A2a3
字符串"a1a2a3",将不是数字2前面的a替换成A。
	public static void test12(){
		String input = "a1a2a3";
		String pattern="a(?!2)+";
		String replacement = "A";
		
		Matcher m = Pattern.compile(pattern).matcher(input);
		log.info(m.replaceAll(replacement));
	}
输出结果:
2017-08-24 09:42:12 - INFO - (RegexTest1.java:118) - A1a2A3

字符边界(\b)和非字符边界(\B)

正则表达式中,\b表示匹配一个字符边界,\B表示匹配一个非资费边界。我们首先理解一下这里的字符指什么?边界指什么?
字符指的是中文英文符号(如:","、"."、"("、"}"、"\"等)、空格、制表符、回车符。
边界指的是单词和字符之间的位置。
注意:一个字符串的开头、结尾和字符之间的位置也是字符边界。
事例1
按照字符边界分割成数组
public static void test9() {
	String input = "a℃a,bb.cc。dd?ee(ff)gg{hh}ii[gg]kk!mm\n我之前是\\n";
	String[] arrayStr = input.split("\\b");
	for (String s : arrayStr) {
		log.info("s[" + s + "]");
	}
}
输出结果:
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[a]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[℃]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[a]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[,]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[bb]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[.]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[cc]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[。]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[dd]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[?]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[ee]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[(]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[ff]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[)]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[gg]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[{]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[hh]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[}]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[ii]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[[]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[gg]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[]]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[kk]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[!]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[mm]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[
]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[我之前是]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[\]
2017-08-23 17:09:40 - INFO - (RegexTest1.java:90) - s[n]
事例2
替换单词边界
public static void test11() {
	String input = "to,today";//to today
	String pattern = "\\b";
	String replacement = "A";

	Matcher m = Pattern.compile(pattern).matcher(input);
	log.info(m.replaceAll(replacement));
}
输出结果:
2017-08-23 17:11:58 - INFO - (RegexTest1.java:108) - AtoA,AtodayA
事例3
要替换一个字符串中的一个单词,但是不替换包含单词的其它单词。“to today”替换to,而不替换today中的to。
	public static void test10() {
		String input = "to,today";//to today
		String pattern = "\\bto\\b";
		String replacement = "go";

		Matcher m = Pattern.compile(pattern).matcher(input);
		log.info(m.replaceAll(replacement));
	}
输出结果:
2017-08-23 17:12:47 - INFO - (RegexTest1.java:100) - go,today

反向引用

捕获组捕获的内容以数字编号的形式存储在内存中,在正则表达式内部可以引用捕获组捕获的内容称为反向引用。也可以在正则表达式外部通过程序进行引用,根据表达式解析的不同,程序在外部引用的方式也不同。一个捕获组(Expression)在匹配成功之前,它的内容可以是不确定的,一旦匹配成功,它的内容就确定了,反向引用的内容也就是确定的了。
反向引用必然要与捕获组一同使用的,如果没有捕获组,而使用了反向引用的语法,不同语言的处理方式不一致,有的语言会抛异常,有的语言会当作普通的转义处理。
对普通捕获组引用语法:\k<number>,通常简写为\number
反向引用应用场景:通常用于查找或限定重复、标识对的配对出现。
事例1
		//查找连续重复两次以上的英文字母
		matcherContent("abbcee1e23","([a-zA-Z])\\1");
输出日志:
2017-08-24 15:21:47 - INFO - (RegexTest2.java:67) - input:abbcee1e23
2017-08-24 15:21:47 - INFO - (RegexTest2.java:68) - pattern:([a-zA-Z])\1
2017-08-24 15:21:47 - INFO - (RegexTest2.java:75) - 第1个匹配模式的子序列.
2017-08-24 15:21:47 - INFO - (RegexTest2.java:76) - group(0):[bb] start:[1] end[3]
2017-08-24 15:21:47 - INFO - (RegexTest2.java:79) - group (1):[b]
2017-08-24 15:21:47 - INFO - (RegexTest2.java:75) - 第2个匹配模式的子序列.
2017-08-24 15:21:47 - INFO - (RegexTest2.java:76) - group(0):[ee] start:[4] end[6]
2017-08-24 15:21:47 - INFO - (RegexTest2.java:79) - group (1):[e]
2017-08-24 15:21:47 - INFO - (RegexTest2.java:82) - --------------END----------------
事例2
		//获取成对出现的HTML标签
		matcherContent("<span>ABC</span><b>我是超人呀!嗯!</b>","<\\s*(\\S+)(\\s[^>]*)?>[\\s\\S]*<\\s*\\/\\1\\s*>");//"(\\<\\w+?\\>)([^\\<].*?)((?:\\<.*?\\>){1}?)");
输出结果:
2017-08-24 15:23:10 - INFO - (RegexTest2.java:67) - input:<span>ABC</span><b>我是超人呀!嗯!</b>
2017-08-24 15:23:10 - INFO - (RegexTest2.java:68) - pattern:<\s*(\S+)(\s[^>]*)?>[\s\S]*<\s*\/\1\s*>
2017-08-24 15:23:10 - INFO - (RegexTest2.java:75) - 第1个匹配模式的子序列.
2017-08-24 15:23:10 - INFO - (RegexTest2.java:76) - group(0):[<span>ABC</span>] start:[0] end[16]
2017-08-24 15:23:10 - INFO - (RegexTest2.java:79) - group (1):[span]
2017-08-24 15:23:10 - INFO - (RegexTest2.java:79) - group (2):[null]
2017-08-24 15:23:10 - INFO - (RegexTest2.java:75) - 第2个匹配模式的子序列.
2017-08-24 15:23:10 - INFO - (RegexTest2.java:76) - group(0):[<b>我是超人呀!嗯!</b>] start:[16] end[31]
2017-08-24 15:23:10 - INFO - (RegexTest2.java:79) - group (1):[b]
2017-08-24 15:23:10 - INFO - (RegexTest2.java:79) - group (2):[null]
2017-08-24 15:23:10 - INFO - (RegexTest2.java:82) - --------------END----------------







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值