1. 基本字符和特殊字符:
1) 正则表达式前面讲过了,是需要先编译再执行的,因此从某种方面来将正则表达式其实也是一种特殊的编程语言;
2) 既然是编程语言(特殊的编程与语言)那么它就有它自己的源代码字符、关键字等概念了;
3) 正则表达式没有像其它编程语言一样的关键字(像if、else、while等),它的关键字是特殊字符,因此正则表达式的源码分为基本字符和特殊字符:
4) 基本字符:包括所有的英文字母(大小写都包含)和数字(0-9),它们就是纯文本字符,它们只代表自己本身;
5) 特殊字符:基本上都是一些符号字符,例如*、\、^等,它们不是纯文本字符,有特殊含义,正则表达式引擎会把它们解释成命令并编译成子函数;
6) 这里先介绍两个最重要的特殊字符,它们在后面会有大量应用:
i. \:转义字符,用于转义紧跟在它后面的下一个字符,使其具有特殊含义;
ii. ():标记子表达式的开始和结束,即用()括起来的部分就是一个子表达式;
2. 匹配单个字符:
1) 基本字符(普通字符):所有的英文字母(包括大小写)和纯数字(0-9),之前已经讲过了;
2) 用ASCII编码来表示字符:有两种形式,一种是8进制形式,一种是16进制形式
i. \0XX:X表示八进制数(0-7);
ii. \xXX:X表示十六进制数(0-9、a-f,不分大小写,,一般推荐大写,大写更美观);
!!由于这两种形式只能表示ASCII编码,而ASCII编码范围是十进制的[0, 127],因此八进制表示法的X最多有2个,而十六进制的X最多也只有两个!!
3) 用Unicode编码来表示字符:\uXXXX
!!X表示十六进制数(字母不区分大小写),必须是4个X,不能缺省(ASCII码表示法可以缺省);
4) 控制符:
i. \t:制表
ii. \n:换行
iii. \r:回车
iv. \a:报警
v. \e:Escape
vi. \cX:Ctrl+X,例如\cM就表示Ctrl+M,其中X是英文字母(包括大小写)
3. 中括号表达式:范围匹配,一种特殊的匹配单个字符的方法
1) 即[ ]表达式,用来匹配单个字符,只不过这一个字符的所在范围有中括号内的表达式来确定
2) 枚举:[abc],表示匹配a、b、c的任意一个
3) 范围:[a-f],表示匹配a-f之间的任意一个字符,包括边界,其中右边界一定要≥左边界,否则引擎编译错误
4) 并:[a-cm-p],就表示a-c的范围和m-p的范围求并,其实前面的abc之类的枚举也是一种并运算
5) 交:[a-z&&b-d],就表示a-z的范围和b-d的范围求交,结果等于[b-d]
6) 补:[^abc]表示非a、b、c的任意一个字符,[^a-f]表示非a-f的任一字符;^也是一个脱字符,必须是中括号表达式的第一个字符,否则不起任何作用!!
!补的是^后面紧跟的整个表达式!
7) 嵌套:[a-m&&[def]],a-m和[def]都表示范围,因此可以做运算,结果等于[d-f]
8) 有了上面这些基本运算,就可以构造一些很复杂的运算了:[a-z&&[^bc]] == [ad-z]
!!但是注意不要用^构造复杂表达式,以下一些表达式将不起作用!
a. ^后面紧跟一个[ ]嵌套:^[a-d],非法!不起任何作用
b. ^后面是一个运算(交并补):^a-cf-h、^a-z&&c-h、^^a-c,都是非法的!!不起任何作用!!
c. ^后面只能跟单纯的枚举和范围运算!!例如[^azh]、[^a-h]等;
4. 前面紧邻的子表达式的重复次数:
1) *:重复0次或多次
2) +:重复1次或多次
!!常用:\w+表示匹配一个单词!
3) ?:重复0次或1次
4) {n}:刚好重复n次
{n,}:重复≥n次,即≥n次的都可以
{n,m}:重复[n, m]次,即n≤ ≤m的都可以
!!不能有任何多余的空格,否则直接编译(引擎编译)不通过;
!!n必须为非负整数,且n≤m,否则也会引擎编译不通过;
!!贪婪模式和勉强模式:解决重复次数匹配的歧义(就是上面的重复次数匹配)
a. 由于重复次数的匹配可能匹配到多种结果,例如:x{2, 3}匹配xxxx,既可以匹配xx,也可以匹配xxx,因此存在歧义;
b. 但是任何编程语言,包括正则表达式,都是不允许有任何歧义的,因此底层必定有避免歧义的机制;
c. 正则表达式引擎默认使用贪婪模式解决歧义问题,贪婪模式:只要符合要求就一直匹配下去,直到无法匹配为止,即上面的结果就是2和3都存在,就往大的取!只取上限,包括*、+、?都是一样的,例如ab.*zz匹配abcxxzzsfewzzq的结果是abcxxzzsfewzz,并没有在abcxxzz处停止,而是直到最后的zzq不能再匹配时才停止匹配!
d. 与贪婪模式对应的就是勉强模式:只匹配下限,即只往少的匹配,一找到符合要求的就立马匹配成功,不再管后面的序列,在勉强模式下*匹配0个,+匹配一个,?匹配0个,而{ }只匹配下限!
f. Java是支持勉强模式的,勉强模式的表示方式是重复次数字符后面紧跟一个?,例如??就表示勉强模式的?,+?就表示勉强模式的+,*?就表示勉强模式的*,{ }?就表示勉强模式的{ };
5. 通配符:
1) .:匹配\r和\n之外的任意其他字符
2) \d:匹配任意一个数字(0-9),即digit
3) \D:匹配任意一个非数字
4) \s:匹配任意一个空白符(制表、换行、空格、换页、回车等),即space
5) \S:匹配任意一个非空白符
6) \w:匹配任意一个单词字符,单词字符指0-9、26个英文字母(包括大小写)、下划线(_),注意是一个字符!而不是一个单词
7) \W:匹配任意一个非单词字符
6. 匹配起始和结尾:锚
1) ^:脱字符,匹配主串的开头,即必须以^后面紧跟的子表达式为开头,例如^abc可以匹配abcxxx,group的返回结果是abc
2) $:锚字符,匹配主串的结尾,即必须以$前面紧跟的子表达式结尾,例如abc$可以匹配qqqxxabc,group的返回结果是abc
3) 组合锚点:例如,^book.*end$可以匹配所有以book起始以end结尾的子串
4) 脱字符必须作为模式串的起始,锚字符必须作为$模式串的结束,否则将不会起到任何作用!!
!例如,abc^xq并不能匹配abcxquuu,而abc$zx并不能匹配abczxddd!!
7. 匹配单词边界:boundary
1) \b:前面紧邻的子表达式必须是单词的边界(单词之间用空白符分隔),例如:ok\b可以匹配"book lala"中的book的ok,但不能匹配"books lala",group返回的也是ok
2) \B:前面紧邻的子表达式必须“不是”单词的边界,例如:ok\B可以匹配"books lala"中的books的ok,但不能匹配"book lala",group返回的也是ok
8. 或运算:|
1) 表示指定两项(表达式)之间任选一项,即或运算;
2) 例如a|b等于[ab],(public)|(private)就表示这两个单词任意匹配一个,但最好不要写成public|private,这可能表示匹配publicrivate或者publiprivate,有些引擎就会判定歧义;
!!良好的习惯:自己不确定是否产生歧义的时候就加一下()变成子表达式,一目了然,而且可以避免错误!!
9. 所有特殊字符的纯文本形式都必须转义!
1) 像.、^、$、\、|、[、]、{、}、(、)等特殊字符,如果想表示其纯文本形式,都必须使用\转义;
2) 例如\(就表示纯文本的'('字符;
10. 在Java源代码中编写正则表达式:
1) 由于正则表达式使用的是自己的字符,而Java源代码也有自己的字符,这就会导致两者之间发生一些不可避免的冲突;
2) 最大的冲突就是\,正则表达式中\就是一个字符(特殊字符),而Java中要表示纯文本的\也需要转义,即用\\来表示纯文本的\;
3) 而Java源代码中编写的正则表达式对于Java源代码来说应该是纯文本,这就意味着,Java源代码中编写的正则表达式中的\都必须要用\\来表示;
4) 例如:正则表达式\w在Java源码中编写就必须写成\\w,比较麻烦
!小结:所有在Java源码中编写的正则表达式中的\都必须用\\表示,如果你觉得上面的原理看着头晕,那就记住这句简单的小结即可,无脑使用;
!!即先正常写好正则表达式,然后把里面所有的\换成\\即可;
5) 一般为了避免这种麻烦的事情,都是先在程序外部的配置文件中写好正则表达式,然后在程序中读取、编译并使用,这就避免了两者字符集的冲突了;