1000行代码徒手写正则表达式引擎【1】--JAVA中正则表达式的使用

简介

本文是系列博客的第一篇,主要讲解和分析正则表达式规则以及JAVA中原生正则表达式引擎的使用。在后续的文章中会涉及基于NFA的正则表达式引擎内部的工作原理,并在此基础上用1000行左右的JAVA代码,实现一个支持常用功能的正则表达式引擎。它支持贪婪匹配和懒惰匹配;支持零宽度字符(如“\b”, “\B”);支持常用字符集(如“\d”, “\s”等);支持自定义字符集(“[a-f]”,“[^b-k] ”等);支持所有重复操作(“*”,“+”,“?”,“{n,m}”等);支持通配符(“. ”);支持运算符本身的转义字符(“\*”,“\.”等);支持命名捕获组。本系列博客的目的是理解正则表达式的内部工作原理,所以没有考虑正则表达式引擎的工作效率以及正负断言和非命名捕获组等不常用的功能,后续的工作将会对其优化并完备其功能。由于正则表达式引擎的实现需要运用数据结构中的相关内容,阅读本系列博客前请熟悉栈,队列,图以及JAVA中容器等相关知识。

欢迎探讨,如有错误敬请指正

如需转载,请注明出处 http://www.cnblogs.com/nullzx/


1. 正则表达式的作用

正则表达式的功能就是在文本串中搜索特定模式的字符串。我们以下面方框中豆瓣电影网页中给出的信息为例,我们想在这些文本中找出所有的日期信息,我们发现日期信息的字符格式在以下文本串中具有特定的格式,都是xxxx-xx-xx的模式(比如2017-01-27),这里的x表示一个具体的数字。所以我们搜索的字符串的格式就是“\d{4}-\d{2}-\d{2}”,在正则表达式中\d表示数字,{n}表示重复n次。


猜火车2

猜火车2 / 迷幻列车2(港) / T2:Trainspotting

2017-01-27(英国) / 伊万·麦克格雷格 / 约翰尼·李·米勒 / 罗伯特·卡莱尔 / 艾文·布莱纳 / 雪莉·亨德森 / 安杰拉·奈迪亚科娃 / 史蒂文·罗伯特森 / 戈登·肯尼迪 / 西蒙·韦尔 / 詹姆斯·卡沙莫 / 梁佩诗 / 阿塔·雅谷伯 / 埃文·威尔什 /...

...

...

...

7.8 (5392人评价)

宝贝老板

宝贝老板 / 娃娃老板 / 波士BB(港)

2017-03-12(迈阿密电影节) / 2017-03-31(美国) / 亚历克·鲍德温 / 迈尔斯·克里斯托弗·巴克什 / 吉米·坎摩尔 / 丽莎·库卓 / 史蒂夫·布西密 / 托比·马奎尔 / 詹姆斯·麦格拉思 / 康拉德·弗农 / 薇薇安·叶 / 小埃里克·贝尔 / 大卫·索伦 / 伊迪·米尔曼...

8.3 (184408人评价)

逃出绝命镇

逃出绝命镇 / 访?吓(港)

2017-01-23(圣丹斯电影节) / 2017-02-24(美国) / 丹尼尔·卡卢亚 / 艾莉森·威廉姆斯 / 凯瑟琳·基纳 / 布莱德利·惠特福德 / 卡赖伯·兰德里·琼斯 / 马库斯·亨德森 / 贝蒂·加布里埃尔 / 勒凯斯·斯坦菲尔德 / 斯蒂芬·鲁特 / 李雷尔·哈瓦瑞...

7.5 (51576人评价)

由于排版的需要,以上文本框中的内容比下图实际处理数据中的内容为基础进行了删减

我们通过正则表达式测试工具进行文本串中特定模式串\d{4}-\d{2}-\d{2}匹配,结果如下图所示

通过得到的处理结果,我们搜索到了文本串中所有的日期信息。从这个例子我们可以看出正则表达式引擎的主要功能就是在给定的文本串中搜索符合正则表达规则的特定模式的字符串,而这个特定的模式是我们通过分析文本串中感兴趣的信息总结得到的一般规律。比如要得到文本中电影的评分,字符串的格式就是“\d.\d”。

除了上述例子外,正则表达式还有很多应用。例如,在注册用户时,验证用户输入的邮箱是否合法;在网络爬虫技术中,爬取我们感兴趣的相关内容;编译器设计中,我们还可以将正则表达式作为词法分析器,等等。使用正则表达式能够使我们更方便,更加高效的解决字符串模式匹配的相关问题,而不必为每一个问题单独写一个程序。这里我们所说的效率高,是指编写程序的效率更高,而非程序的运行效率。

我们的目的是写一个正则表达式引擎,所以我们接下来就非常有必要了解一下正则表达式的一般规则。

2. JAVA正则表达式的规则

2.1 自定义字符集


[abc]


a或b或c


[^abc]


除了a,b,c的其它字符


[a-zA-Z]


满足a-z范围的字符或A-Z范围的字符

例子:下面的正则表达式会匹配两个字符,第一个为小写字母,第二个为数字,文本串中已捕获的内容用红色表示。

正则表达式:“[a-z][0-9]”

文本串内容:“absef809sefdk1dfes12389”

2.2 已定义字符集


.


可以匹配非换行符以外的任何字符,能否匹配换行符是可配置的


\d


数字,等价于[0-9]


\D


非数字,等价于[^0-9]


\s


空白符,等价于[ \t\n\x0B\f\r]


\S


非空白符,等价于[^\s]


\w


字母、数字或下划线,等价于[a-zA-Z_0-9]


\W


非字母和数字,等价于[^\w]

例子:下面的正则表达式会匹配以非空白字符开头和非空白字符结尾,中间是“abc”的字符串,总共需要捕获5个字符,文本串中已捕获的内容用红色表示。

正则表达式:“\Sabc\S”

文本串内容:“abcd abc defabcyjkabc”

2.3 转义字符(不解释)


\t


The tab character (‘\u0009‘)


\n


The newline (line feed) character (‘\u000A‘)


\r


The carriage-return character (‘\u000D‘)


\f


The form-feed character (‘\u000C‘)


\a


The alert (bell) character (‘\u0007‘)


\e


The escape character (‘\u001B‘)


\cx


The control character corresponding to x

2.4 零宽度边界字符

零宽度边界字符,只会匹配一个位置而不会占有字符


^


行开始


$


行结束


\b


单词的开始边界或结束边界


\B


非单词的边界

例子:下面的正则表达式会匹配字符串“abc”,并且要求第一个字符‘a’的前面不是字母字符和数字字符,最后一个字符‘c’的后面不是字母字符和数字字符。正则表达式总共需要捕获3个字符,文本串中已捕获的内容用红色表示。

正则表达式:“\babc\b”

文本串内容:“abc dabcd abc abcd -abc”

2.5 贪婪重复模式(尽量多重复)

X表示一个合法的正则表达式


X?


X重复一次或0次


X*


X,重复0次或多次


X+


X重复至少1次


X{n}


X重复刚好n次


X{n,}


X重复至少n次


X{n,m}


X重复至少n次,最多m次

例子:下面的正则表达式会匹配以a开头和a界结尾的,中间有尽可能多的其它字符,且其它字符要求至少有一次,文本串中已捕获的内容用红色表示。

正则表达式:“a.+a”

文本串内容:“zxyabcdefasseaa09876”

2.6 懒惰重复模式(尽量少重复)


X??


X重复一次或0次


X*?


X,重复0次或多次


X+?


X重复至少1次


X{n}?


X重复刚好n次


X{n,}?


X重复至少n次


X{n,m}?


X重复至少n次,最多m次

例子:下面的正则表达式会匹配以a开头和a界结尾的,中间有尽可能少的其它任意字符,且其它任意字符要求至少有一次。文本串中已捕获的内容用红色表示。

正则表达式:“a.+?a”

文本串内容:“zxyabcdefasseaa09876”

2.7 逻辑运算符

X和Y分别表示两个正则表达式

XY先满足正则表达式X,然后满足正则表达式Y的正则表达式

X|Y 满足正则表达式X或满足正则表达式Y的正则表达式

注意优先级,X|YZ 等价于 X|(YZ),而(X|Y)Z表示XZ|YZ

正则表达式:“a(b|c)d”

文本串内容:“zxyabcdefacdeaabd876”

2.8 括号

在正则表达式中的作用有两个,一个和四则运算中的括号相同,用来改变优先级,另一个用于分组捕获。分组捕获又分为两种,一种是自定义命名的分组,还有一种是未命名的分组(或者称为自动编号分组)。

命名分组的格式为:(?<name>X),其中X表示一个正则表达式

例子:下面的正则表达式表示已数字开头,中间是字母,以数字结尾的字符串。名为letter的捕获组捕获符合该正则表达式中间为字母的部分。文本串中捕获的内容用红色表示。

正则表达式:“\d+(?<letter>[a-zA-Z]+)\d+”

文本串内容:“0123ab456def gisd4ZDG6zz”

名为letter的捕获组中的内容为:“0123ab456def gisd4ZDG6zz”

对于未命名分组,每一对括号实际上都是一对分组,正则表达式引擎会在编译该表达式的时候会从左到右扫描正则表达式,对未命名分组进行编号。遇到的第1个左括号(和相应匹配的右括号)是第1组,遇到的第2个左括号(和相应匹配的右括号)是第2组,……。第0组的内容匹配的是整个正则表达式。实际上组号分配过程是要从左向右扫描两遍的:第一遍只给未命名组分配,第二遍只给自定义命名组分配,也就是说自定义命名分组也是有编号的,且所有自定义命名组的组号都大于未命名的组号。

2.9 特殊字符的匹配

对于一些在正则表达式中具有特殊含义的特殊字符,比如“{”,“*”“\”等等,如果我们想在文本串中捕获它们,就只能通过转义的方式。比如我们想匹配文本串中以“{”花括号开头,花括号结尾“}”,中间有任意数量其它字符,且其它任意字符尽可能少。我们的正则表达式就可以写成“\{.*?\}”,它可以匹配以下字符串中的“abcde{fghi{jklmn}op}xyz”。

正则表达式:“\\.*?\\” 表示已“\”开头和“\”结尾中间为任意数量且尽可能少的其它字符。它可以匹配以下字符串中的“abcde\fghi\jklmn\op\xyz”

2.10 零宽断言

在某些特殊的情况下,如果我们只是想要匹配某个字符有(或者没有)出现,但并不想去捕获它的时候,我们就需要零宽度断言。零宽度断言和\b等零宽度字符一样,都是匹配一个位置,并不消耗字符,但零宽度断言可以是由表达式构成,功能也就更加强大。零宽度断言分为四种情况。

零宽度正预测断言

“预测”表示向匹配内容的后方看,“正”表示匹配的意思

一般格式:“exp1(?=exp2)”

含义:匹配文本串中符合正则表达式exp1的内容,且文本串中已匹配exp1的字符串的后面必须匹配exp2,但不消耗文本串中匹配exp2的字符,且结果中不捕获exp2匹配的内容。不消耗匹配exp2的字符的意思是,下一次搜索从文本串中匹配exp1的后面开始,而不是从匹配exp2的后面开始。注意,exp2右括号后面一般不能再跟正则表达式否则,不会匹配到任何东西。

例子:下面的正则表达式会匹配一个单词,且这个单词必须以ing结尾。文本串中捕获的内容用红色标示,绿色表示正预言的匹配。

正则表达式:"\b\w+(?=ing\b)"

文本串内容:"i am singing while you are dancing"

注意,正则表达式不能写成"\b\w+(?=ing)\b",这样不会匹配任何字符串,因为不存在任何一个字符串后面是ing,同时又要求ing是结束的边界(由于不消耗文本串中的ing)。

同理,"\b\w+(?=ing)ing" 等价于 "\b\w+ing"

零宽度正回顾断言

“回顾”表示向匹配内容的前方看,“正”表示匹配的意思

一般格式:“(?<=exp2)exp1”

含义:匹配文本串中符合正则表达式exp1的内容,且文本串中已匹配exp1的内容前面必须匹配exp2,但在结果中不捕获exp2的匹配内容。注意,exp2左括号前面不能再跟正则表达式否则,不会匹配到任何东西。

例子:下面的正则表达式会匹配一个单词,且这个单词必须以anti开头。文本串中捕获的内容用红色标示,绿色表示正回顾的匹配。

正则表达式:"(?<=\banti)\w+\b"

文本串内容:"this is an antibody, not an antivirus"

注意,正则表达式不能写成"\b(?<=anti)\w+\b",原因和正预测是一样的,因为“\b”和“anti”是互斥的,也就是说没有一个字符同时满足即使“\b”又是“anti”。

零宽度负预测断言

“预测”表示向匹配内容的后方看,“负”表示不匹配的意思

一般格式:“exp1(?!exp2)”

含义:匹配文本串中符合正则表达式exp1的内容,且匹配exp1的内容后面不能匹配exp2。和正预测不同,我们一般可以构成如下的正则表达式exp1(?!exp2)exp3,只要exp2和exp3不相同就不会构成互斥。

例子:下面的正则表达式会匹配一个单词,且这个单词不能以ing结尾。文本串中捕获的内容用红色标示。

例子:下面的正则表达式会匹配一个单词,且这个单词不能以anti开头。文本串中捕获的内容用红色标示。

正则表达式:"\\b(?!anti)\\w+\\b"

文本串:"this is an antibody, not an antivirus"

零宽度负回顾断言

“回顾”表示向匹配内容的前方看,“负”表示不匹配的意思

一般格式:“(?<!exp2)exp1”

含义:捕获文本串中符合正则表达式exp1的内容,且捕获的内容前面不能匹配exp2。和正回顾不同,我们一般可以构成如下的正则表达式“exp3(?<!exp2)exp1”,只要exp2和exp3不相同就会构成互斥。

例子:下面的正则表达式会匹配一个单词,且这个单词不能以ing结尾。文本串中捕获的内容用红色标示。

正则表达式:"\\b\\w+(?<!ing)\\b";

文本串:"i am singing , you are danceing";

3. JAVA中正则表达式引擎的使用

对同一个正则表达式,从键盘上输入的形式和程序中由字符串表示的正则表达式的形式是不同的。比如我们最开始举例时使用的正则表达式 \d{4}-\d{2}-\d{2} ,在JAVA中用字符串表示的形式如下

String reg = “\\d{4}-\\d{2}-\\{2}”

因为在字符串中,需要用两个“\\”表示一个“\”

JAVA中使用正则表达式主要涉及到两个类,一个是Pattern类,另一个是Matcher类,他们都位于java.util.regex包中。Pattern主要的功能就是将正则表达式转换成NFA(不确定有限自动状态机)或者DFA(确定有限自动状态机)。Matcher的作用是通过Pattern产生的FNA或DFA对文本串进行匹配。

Pattern类的构造函数:

public static Pattern compile(String regex)

public static Pattern compile(String regex, int flags)第二个构造函数中的flag,可以是下列属性值的组合,它们会影响匹配结果。


static int


CANON_EQ

Enables canonical equivalence.


static int


CASE_INSENSITIVE

大小写不敏感


static int


COMMENTS

允许正则表达式中出现注释

默认情况下正则表达式中不允许出现正则表达式规定的注释


static int


DOTALL

“.”可以匹配任何字符

默认情况下不能匹配 “\r\n”和“\n”


static int


LITERAL

该模式下将转义字符就表示字符本身

“\\d”就表示一个“\”和“d”而不表示数字的字符集


static int


MULTILINE

多行模式:^表示字符串中每一行的开始,$表示字符串中每一行的结束

默认情况下:^表示字符串的开始,$表示字符串的结束


static int


UNICODE_CASE

Enables Unicode-aware case folding.


static int


UNICODE_CHARACTER_CLASS

使用该选项使得原先已定义好的字符集兼容Unicode编码


static int


UNIX_LINES

类Unix下的换行:“\n”

默认情况下使用Windows下的换行,即:“\r\n”或者 “\n”

Pattern类下还有一个matcher方法,我们通过该matcher方法产生Matcher对象,该方法参数表示待匹配的文本串。

public Matcher matcher(CharSequence input)Matcher下的find方法用于对文本串的匹配,如果发现匹配就返回真,当再次调用find函数时,会从上次已匹配的位置继续搜索。

Matcher下的group方法用于返回匹配的字符串,start和end方法用于返回匹配的字符串在文本串中的开始和结束位置,注意不包含结束位置。

package javalearning;

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

public class RegularExpTest {
	public static void main(String[] args){

		String reg = "[a-z]((?<digtal>\\d+)(b|c)d)[A-Z]";
		String txt = "zdfasdfre1234bdXrt";

		Pattern p = Pattern.compile(reg);
		Matcher m = p.matcher(txt);

		while(m.find()){
			System.out.println("--------匹配结果----------");
			System.out.printf("[%2d, %2d) : %s\n", m.start(), m.end(), m.group());
			System.out.println("--------自动命名组匹配结果--------");
			for(int i = 0; i < m.groupCount(); i++){
				System.out.printf("group %2d  [%2d, %2d) : %s\n",i, m.start(i), m.end(i), m.group(i));
			}
			System.out.println("--------自定义命名组匹配结果--------");
			System.out.printf("digtal    [%2d, %2d) : %s\n", m.start("digtal"), m.end("digtal"), m.group("digtal"));
			System.out.println();
			System.out.println();
			System.out.println();
		}
	}
}

运行结果


--------匹配结果----------

[ 8, 16) : e1234bdX

--------自动命名组匹配结果--------

group 0 [ 8, 16) : e1234bdX

group 1 [ 9, 15) : 1234bd

group 2 [ 9, 13) : 1234

--------自定义命名组匹配结果--------

digtal [ 9, 13) : 1234

4. 参考内容

[1] 30分钟学会正则表达式

[2] 正则表达式在线测试工具

[3] 正则表达式在线测试工具

时间: 2024-10-13 21:55:20

1000行代码徒手写正则表达式引擎【1】--JAVA中正则表达式的使用的相关文章

javascript学习笔记-正则表达式-少写1000行代码的正则表达式

正则表达式的要点  :  查找   匹配   替换 程序员必知:让你少写1000行代码的20个正则表达式 一个十分古老而又强大的文本处理工具,仅仅用一段非常简短的表达式语句,便能够快速实现一个非常复杂的业务逻辑.熟练地掌握正则表达式的话,能够使你的开发效率得到极大的提升. 正则表达式经常被用于字段或任意字符串的校验,如下面这段校验基本日期格式的Java代码: 下面是蓝鸥HTML5培训小编整理的,在前端开发中经常使用到的20个正则表达式. 1 . 校验密码强度 密码的强度必须是包含大小写字母和数字

1000行代码写小游戏(终)

最后献上完整的1000行代码,基本功能已经完成,可以通过配置小怪和矿的位置和大小控制玩家时长和难度: -------------------------------------------------------------------------------------------- -- Added by ??? ------------------------------------------------------------------------------------------

Java中正则表达式的使用(常用的方法)

这两天回想了一下正则表达式的使用,顺便就总结了一下java的javascript中使用正则表达式的用法,需要看javascript中使用正则的朋友可以看我的另一篇总结,下面我就简单的介绍一下java中正则表达式的使用.方便自己以后查询使用,也希望能帮助到大家.===欢迎指正=== 在JDK1.3及之前的JDK版本中并没有包含正则表达式的类,如果要在Java中使用正则表达式必须使用第三方提供的正则表达式库.从JDK1.4开始提供了支持正则表达式API,它们位于java.util.regex包中.

js和java中正则表达式的易混淆点

js中正则表达式的使用 对表单中的值进行正则表达式匹配一般有两种方法: var reg = new RegExp(regStr); reg.test(value); 如下: var reg = new RegExp("\\d{3}"); reg.test("abc123def"); 结果:true 注:new RegExp中传的是正则表达式的字符串,\需要用\转义. str.match(regex);      如"abc123def".matc

java中正则表达式基本用法

正则表达式是一种可以用于模式匹配和替换的规范,一个正则表达式就是由普通的字符(例如字符a到z)以及特殊字符(元字符)组成的文字模式,它 用以描述在查找文字主体时待匹配的一个或多个字符串.正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配. 众所周知,在程序开发中,难免会遇到需要匹配.查找.替换.判断字符串的情况发生,而这些情况有时又比较复杂,如果用纯编码方式解决,往往会浪费程序员的时间及精力.因此,学习及使用正则表达式,便成了解决这一矛盾的主要手段. 大家都知道,正则表达式是一种可以

java基础----&gt;java中正则表达式二

跟正则表达式相关的类有:Pattern.Matcher和String.今天我们就开始Java中正则表达式的学习. Pattern和Matcher的理解 一.正则表达式的使用方法 一般推荐使用的方式如下: Pattern pattern = Pattern.compile("^[^abc]h$"); Matcher matcher = pattern.matcher("hh"); boolean isMatch = matcher.matches(); 另外一种不能复

java中正则表达式基本用法(转)

https://www.cnblogs.com/xhj123/p/6032683.html 正则表达式是一种可以用于模式匹配和替换的规范,一个正则表达式就是由普通的字符(例如字符a到z)以及特殊字符(元字符)组成的文字模式,它 用以描述在查找文字主体时待匹配的一个或多个字符串.正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配. 众所周知,在程序开发中,难免会遇到需要匹配.查找.替换.判断字符串的情况发生,而这些情况有时又比较复杂,如果用纯编码方式解决,往往会浪费程序员的时间及精力.

java中 正则表达式的使用

推荐使用第一种 第一种: //对接收的文件名的合法性进行验证 String fileName="127.0.0.1_01_20140428165022174.jpg"; String regEx = "\\b.+_\\d+_\\d{17}\\b"; //正则表达式 Pattern p = Pattern.compile(regEx); Matcher m = p.matcher(fileName); if(!m.find()){ SysLog.logger.erro

Java中正则表达式的使用

在Java中,我们为了查找某个给定字符串中是否有需要查找的某个字符或者子字串.或者对字符串进行分割.或者对字符串一些字符进行替换/删除,一般会通过if-else.for 的配合使用来实现这些功能 .如下所示: Java代码   public class Test{ public static void main(String args[]) { String str="@Shang Hai Hong Qiao Fei Ji Chang"; boolean rs = false; for