JAVA的正则表达式
一、前言
正则表达式这个东西,基本哪一种语言都有。
例如数据库的oracle,前后端高级编程语言。
很多工具也支持正则,例如至少ue,Notepad++,好一点的编程ide(eclipse,idea,vscode)。
正则实在是一个利器,是程序员必须掌握的一个技能。
如果工作了几年,正则还用不明白,那么算不上是一个合格的程序员。
二、概念
正则表达式这个概念是外国人先提起来的,英文regular expression。
regular翻译过来的几个含义(参见regular (youdao.com)):
恒定的,规则的(尤指间隔相同);经常做(或发生)的,频繁的;经常做某事的,常去某地的;惯常的,通常的;持久的,固定的;<美>标准尺寸的,中号的;普通的,平凡的;常备军的,正规军的;(动词或名词)按规则变化的;(人)正常通便的,月经正常的;<非正式> 完全的,彻底的;(花)呈放射状对称的;等边的,匀称的;(冲浪等有板运动用语)左腿在前的;(人)受过适当培训(或取得适当资格)并从事全职工作的,有正式工作的;符合手续(或规定)的,正当的;(基督教)受教规约束的,属于修道会的
结合个人的体验,应该用的“规则的”这个意思。
那么为什么汉语会把regular expression 翻译为正则表达式/正则式? 这个“正“是什么意思?
这个问题许多人都有疑问,部分人给出了答案:
1.实用角度,如果总用“规则”,那么不容易确定是什么“规则”,可能较为容易浪费一些时间。
2.其次,“正”这里做动词用,意思是规整,归正,端正。例如“居者思正其家,行者乐出其途。——柳宗元《全义县复北门记》“”
所以,“正则表达式”的准确含义是:使得文本符合规则的表达式。
三、体系
正则表达式起源:https://blog.csdn.net/weixin_43735348/article/details/101516794
正则表达式起源于1951年,当时数学家Stephen Cole Kleene使用他的称为正则事件的数学符号描述了正则语言。这些出现在理论计算机科学,自动机理论(计算模型)以及形式语言的描述和分类的子领域中。
模式匹配的其他早期实现包括SNOBOL语言,该语言不使用正则表达式,而是使用其自己的模式匹配结构。
从1968年开始,正则表达式有两种用法:在文本编辑器中进行模式匹配和在编译器中进行词法分析。
程序形式的正则表达式的首次出现是Ken Thompson将Kleene的符号内置到编辑器QED中的一种方式,以匹配文本文件中的模式。
为了提高速度,Thompson通过即时编译(JIT)对Compatible Time-Sharing System上的IBM 7094代码实施了正则表达式匹配,这是JIT编译的重要早期示例。
后来,他将此功能添加到Unix编辑器ed中,最终导致了流行的搜索工具grep使用正则表达式(“ grep”是从ed编辑器中用于正则表达式搜索的命令衍生的单词:g / re / p表示“全局搜索正则表达式和打印匹配行”)。
在汤普森开发QED的同时,包括Douglas T. Ross在内的一组研究人员实现了一种基于正则表达式的工具,该工具用于编译器设计中的词法分析。
这些原始形式的正则表达式的许多变体在1970年代的Bell Labs的Unix 程序中使用,包括vi,lex,sed,AWK和expr,以及其他程序(例如Emacs)。随后,正则表达式被各种程序采用,这些早期形式在1992年的POSIX.2标准中得到了标准化。
在1980年代,Perl中出现了更复杂的正则表达式,最初是由Henry Spencer(1986)编写的正则表达式库派生的,后者后来为Tcl编写了高级正则表达式的实现。
Tcl库是具有改进的性能特征的NFA / DFA混合实现。采用Spencer Tcl正则表达式实现的软件项目包括PostgreSQL。 Perl随后扩展了Spencer的原始库,以添加许多新功能。
Perl 6设计的部分工作是改善Perl的正则表达式集成,并增加其范围和功能,以允许定义解析表达式语法。结果是一种称为Perl 6规则的迷你语言,该规则用于定义Perl 6语法并为使用该语言的程序员提供工具。
这些规则保留了Perl 5.x正则表达式的现有功能,但也允许通过子规则以BNF样式定义递归下降解析器。
正则表达式在结构化信息标准中用于文档和数据库建模的使用始于1960年代,并在1980年代得到扩展,当时,诸如ISO SGML(由ANSI“ GCA 101-1983”取代)的行业标准得到了巩固。结构规范语言标准的内核由正则表达式组成。它的使用在DTD元素组语法中很明显。
Philip Hazel从1997年开始开发了PCRE(与Perl兼容的正则表达式),它试图紧密模仿Perl的正则表达式功能,并被许多现代工具所使用,包括PHP和Apache HTTP Server。
如今,正则表达式在编程语言,文本处理程序(尤其是词法分析器),高级文本编辑器和其他一些程序中得到广泛支持。
正则表达式支持是许多编程语言(包括Java和Python)的标准库的一部分,并内置于其他语言(包括Perl和ECMAScript)的语法中。正则表达式功能的实现通常称为正则表达式引擎,许多库可供重用。
几个关键字:
1.1951
2.Stephen Cole Kleene(数学家),汉译:史蒂芬.科尔.克莱尼
ken Thompson ,汉译 肯.汤普生
3.posix
4.perl
5.PCRE,apacheHttp server
正则表达式有一个共有的根,但是有两套小的规则:基于Posix和基于perl的。从前文可以看出perl的实现更加的丰富。
从javadoc看,java是NFA-based实现的,但和perl5相差无几,是基于肯.汤普生的。
关于正则的引擎一些内容,可以参见博客:http://www.cppblog.com/airtrack/archive/2014/09/15/208319.html
该博主应该是非常深入和专业地解释了有关正则的有关内容。
这里抄录一些:
正则引擎常见的实现方法
正则的常见实现方式有三种:DFA、Backtracking、NFA:
DFA是三种实现中效率最高的,不过缺点也明显,一是DFA的构造复杂耗时,二是DFA支持的正则语法有限。
在早期正则被发明出来时,只有concatenation、alternation、Kleene star,即"ab" "a|b" "a*",DFA可以轻松搞定。
随着计算机的发展,正则像所有其它语言一样发展出各种新的语法,很多语法在DFA中难以实现,比如capture、backreference(capture倒是有论文描述可以在DFA中实现)。
Backtracking是三种实现中效率最低的,功能确是最强的,它可以实现所有后面新加的语法,因此,大多数正则引擎实现都采用此方法。因为它是回溯的,所以在某些情况下会出现指数复杂度,这篇文章有详细的描述。
NFA(Thompson NFA)有相对DFA来说的构造简单,并兼有接近DFA的效率,并且在面对Backtracking出现指数复杂度时的正则表达式保持良好的性能。
小结:
1.1951 史蒂芬.科尔.克莱尼
2.两个体系 posix,perl
3.三个实现:DFA,backtracking(回溯),NFA
其中
DFA:Deterministic Finite State Automata 确定的有穷自动机
NFA:Non-Deeterministic Finite State Automata 无确定的有穷自动机
四、规则介绍和java的实现说明
本章节有选择地翻译自javaDoc(jdk17),包含了大部分的内容,舍弃了和规则介绍无关的一些内容。
j事实上从jdk1.8到jdk17,java好像没有改变NFA的实现--没有增加新的语法或者是优化了算法(这些是个人臆测,从javadoc内容猜测)
4.1 基本的正则表达式结构类
在javaDoc中使用了construct,这里应该是结构(结构体,构件)的意思。
javaDoc把后文表格中列出的表达式称为结构(组件),大体分为几个:
字符识别
a.字符--表达特定的单个字符
b.字符类-表达一类字符,必须和符号[],^,-,&&一起使用。
【】--集合
^-不含
&& 且
- 到
c.预定义字符类-表达一类的字符,但这是为了方便而预先定义的。不表示具体某个字符,例如 \d表示阿拉伯数字[0-9]
d.POSIX字符类(US-ASCII),以\p{}表示格式,功效上和预定义字符类差不多,就是为了方便
e.java.lang.Character 类,非常特别的一类,java特有的。格式为 \p{}
f.统一脚本,块,分类和二进制属性.格式为格式为 \p{}
以上a-f类是用于识别字符
边界
某种程度上类似预定义类,用于表示输入边界,匹配边界。其中关于字符边界(范围)是比较特殊的,似乎不是一类
贪心限定
表示匹配的个数。例如*,?
保守限定
基本同贪心限定,或者等同于贪心限定后更上一个?。不清楚有什么实际区别
占有限定
基本同贪心限定,或者等同于贪心限定后更上一个+。不清楚有什么实际区别
逻辑操作符
用于在一个表达式中实现多个匹配。具体有三种:跟随(没有符号);|(或者);()定义分组
回溯符/向后符
定义已经匹配的内容。非常常用的是\n.
引用
对已经捕获的分组的引用。允许通过名称或者捕获的顺序进行匹配
特别的结构(命名捕获和非捕获)
4.2 JAVA正则结构类明细表
表:java支持的结构类 明细
分类 | 表达式 | 英文 | 中文 | 说明 |
字符 | x | 字符x | x是一个符号,具体写的时候,应该是如 a,b,c,1,2,3,中之类的 | |
字符 | \\ | 反斜杆 | ||
字符 | \0n | 八进制数 | n介于[0,7] | |
字符 | \0nn | 八进制数 | n介于[0,7] | |
字符 | \0mnn | 八进制数 |
m介于[0,3] n介于[0-7] |
|
字符 | \xhh | 16进制数 | 表示0xhh | |
字符 | \uhhhh | 16进制数 | 表示0xhhhh | |
字符 | \x{h...h} | 16进制数 |
表示0xh..h 值介于[0, |
|
字符 | \N{name} | 统一码 | 表示名称为'name'的统一码 | |
字符 | \t | tab | 对应\u0009 | |
字符 | \n | 换行符 | 对应\u000A | |
字符 | \r | 回车符 | 对应\u000D | |
字符 | \f | 进表符/换页符 | 对应\u000C | |
字符 | \a | 响铃符 | 对应\u0007 | |
字符 | \e | 逃逸符 | 对应\u001B | |
字符 | \cx | 控制符x |
x是控制符,写的时候需要具体化。 例如\cC |
|
字符类 | [abc] | 简单类 |
分组符号[]中的每个元素都是一个具体的字符,也就是说abc可以是仁义字符,个数 也可以是任意的 重点:【】 |
|
字符类 | [^abc] | 简单类的非集 |
即不包含abc这几个字符 重点:^ |
|
字符类 | [a-zA-Z] | 包含多个类型的分类 |
意思是在一个集合符号[]内,可以有多种类型的字符,而且同一种字符之间可以使用横杆 来表示开始和结束的范围。注意,这可以多个类型。例如实际可以这样: [a-L0-9] 重点:- |
|
字符类 | [a-d[m-p]] | 并集分类 |
等同于包含多个类型的分类:[a-dm-p] 重点:仅仅为了方便 |
|
字符类 | [a-z&&[def]] | 交集分类 |
本例 等同于[def] 重点: && |
|
字符类 |
[a-z&&[^bc]] [a-z&&[&m-p]] |
&& ,^,-的运算 | 即[],-,&&可以有多重关系 | |
预定义字符类 | . | 任意字符 |
. 任意字符 \d 数字,d是digital的首字母 \D 非数字,等同于[^0-9] \h 水平空格字符 \H 非水平空格字符,等同于[^h] \s 空格字符:[\t\n\x0B\f\r] \S 非空格字符 \v 垂直空格字符 \V 非垂直空格字符 \w 单字字符:[a-zA-Z_0-9] \W:非单字字符[^w] |
|
posix 字符类 | \p{Lower}} A lower-case alphabetic character: [a-z] 小写字母 \p{Upper}} An upper-case alphabetic character:[A-Z] 答谢字母 \p{ASCII}} All ASCII:[\x00-\x7F] ASCII字符 \p{Alpha}} An alphabetic character: [\p{Lower}\p{Upper}]} 字母,函大小写=[a-zA-Z] \p{Digit}} A decimal digit: [0-9] 数字 \p{Alnum}} An alphanumeric character: [\p{Alpha}\p{Digit}]} 字母和数字 \p{Punct}} Punctuation: One of !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~} 标点符号 \p{Graph}} A visible character: [\p{Alnum}\p{Punct}]} 可见字符=字母+数字+标点符号 \p{Print}} A printable character: [\p{Graph}\x20]} 打印字符 \p{Blank}} A space or a tab: [ \t] 空或者tab \p{Cntrl}} A control character: [\x00-\x1F\x7F] 控制字符 \p{XDigit}} A hexadecimal digit: [0-9a-fA-F] 16进制有关字符=数字+[a-fA-F] \p{Space}} A whitespace character: [ \t\n\x0B\f\r] 空格 |
|||
java.lang.Character类 | \p{javaLowerCase}} Equivalent to java.lang.Character.isLowerCase() 、 \p{javaUpperCase}} Equivalent to java.lang.Character.isUpperCase() \p{javaWhitespace}} Equivalent to java.lang.Character.isWhitespace() \p{javaMirrored}} Equivalent to java.lang.Character.isMirrored() |
|||
统一脚本,块,分类和二进制属性 | \p{IsLatin}} A Latin script character (script) 拉丁字符 \p{InGreek}} A character in the Greek block (block) 希腊字符 \p{Lu}} An uppercase letter (category) 大写字 \p{IsAlphabetic}} An alphabetic character (binary property) 字母字符 \p{Sc}} A currency symbol 货币符号 \P{InGreek}} Any character except one in the Greek block (negation) 非希腊字符? [\p{L}&&[^\p{Lu}]]} Any letter except an uppercase letter (subtraction) 非大写字符? |
|||
边界类(重点) | ^ | 行开始,或者表示以什么开始。用的时候是后面更上其它结构 | ||
边界类(重点) | $ | 行结尾。用的时候,是前面跟上其它结构 | ||
边界类(重点) | \b |
等同于\w ,这个需要确定 |
||
边界类(重点) | \b{g}} | 统一符+其它字素 ? | ||
边界类(重点) | \B | 等同于[^\w] | ||
边界类(重点) | \G | 前一个匹配之后。 之后的什么? | ||
边界类(重点) | \Z | 输入的最后一个终止符 ? | ||
边界类(重点) | \z | 输入结尾 | ||
贪心限定(重点) |
? 一个或者没有 <=1 * 没有或者多个 >=0 + 至少一个 >1 {n} n个 {n,} 至少n个 {n,m} 个数介于[n,m]之间 |
|||
保守限定 | 注:是在贪心限定之后跟上一个? .含义是一样的,不知道这是什么意思 | |||
占有限定 | 注:是在贪心限定之后跟上一个+.含义是一样的,不知道这是什么意思 | |||
逻辑操作符号 | 跟随 |
例如 ab. 注意在集合符号[]内“跟随”不生效,例如[ax],并不是说a后面跟着x,而是包含a或者x |
||
逻辑操作符号 | | | 或者 | 这个容易理解。常常用于一次要匹配多种情况的表达式中 | |
逻辑操作符号 | () | 分组 |
如何为分组命名? (?<name>X) 其中这个X就是分组内的规则(结构) |
|
回溯符/向后引用 | \n | 匹配到第n个组 | n>=1 | |
\k<name> | 按名字查找匹配的分组 | |||
特别结构类(命名和不合法捕获?) | (?<name>X) | 为分组命名 | X是分组内的规则,例如 (?<char>[a-z]) | |
(?: X) |
无效分组 |
所有其它无效捕获的分组?(non-capturing -- 无效/不合法捕获)。 注意,non不是none(没有) 表示“(?:X)“这个是不能有的分组,必须排除的。 有时候,可能使用^还不够方便,还要对分组排除,这样更方便,所有有这么一个东西。 a(?:\d)b 可以匹配abc,不能匹配a1b,因为a的右边不能有数字 |
||
(?= X) |
右边是(相对某个) |
X, via zero-width positive lookahead lookahead --向前看,即根据从左向右的查看和输入(验证时候字符的输入顺序)规则,向前即向右边看。 但这里隐藏了一个很重要的东西:相对于什么。 所以这个构件前如有有其它构件,那么意思就是前面一个构件之后必须是这个分组。 后文的lookbehind,和lookahead是相反的意思,也有类似的隐藏条件:相对于什么向后(左边)。
= 表示满足条件,但不捕获(find) ! 表示不满足,但捕获(find) 下同 |
||
(?! X) |
右边不是(相对某个) |
X, via zero-width negative lookahead 基本同?=x,不过意思是某个构件后不能有这个分组 |
||
(?<= X) |
左边是(相对某个) |
X, via zero-width positive lookbehind 基本同?=x,不过多了一个<,意思是后面(相对于某个构件,其左边)的构件是x |
||
(?<! X) |
左边不是(相对某个) |
X, via zero-width negative lookbehind 基本同?!x,不过多了一个<,意思是后面(相对于某个构件,其左边)的构件不是x |
||
(?> X) |
X, as an independent, non-capturing group
|
注:上表没有列出所有的结构信息,部分比较奇特的没有包含在内。
关于特别构件/机构类,可以参见 https://blog.csdn.net/ZHOUBANGDING/article/details/54378373
javadoc存心想让人看不明白!
五、重点要掌握什么
正则表达式的规则总体来说还是有限,但限于文档,许多规则无法通过一篇文章说清楚。
许多时候,我们需要通过测试用例来验证表达式的正确性,这也是为什么有许多正则表达式工具的原因。
而我们学习(数量掌握)的目的并不是为了仅仅掌握如何用这些工具,而是为了提高我们的效率:可以在大部分情况下很好滴写出需要的表达式。
1.基本符号含义
这些符号包括 :
- [ ] -- 集合符号
- - --连接符号,和其它两个字符一起表示一个字符范围
- ^ --不包含或者是从行头开始
- && -- 集合运算符且,不是逻辑运算符号
- | -- 逻辑操作符号,表示只要匹配的时候,满足其中一种情况即可
- * -- 匹配个数:>=0
- ? -- 匹配一个或者其它用途(有点多)
- + -- 匹配个数:>=1
- () -- 分组符号
2.转义符(escape)
如果正则表达式中要表示匹配特殊的字符,例如+-,怎么办? 和大部分语言一样:在字符前面添加一个反斜杠\。例如 \-,\+分别表示匹配-+。
3.特别字符的表示
\n,\r表示换行,还有其它一些,具体查前表。
4.分组
在perl规则中,分组非常好用。
为什么要分组? 因为我们希望对匹配的内容进行再处理,最好的方式是对目标对象进行分组。
举个简单的例子,我们希望查找文本中所有字母开头后跟数字的词语,并把所有的首字母转成小写,那么可以这样写正则表达式:
([a-z])([a-z0-9_]+)
当我们找到后,就可以对分组1进行操作。
5.逻辑操作 | &&
6.不同结构之间的关系
什么分类之间可以互相组合?什么分类可以互相包含?
a.结构和结构之间不是可以任意组合的
b.有的结构可以包含其它结构,有的不行
c.理解向后引用,包括怎么算分组序号,怎么使用。能够有效利用向后引用,可以实现一些复杂的查找
只要记住几条主要的注意事项即可:
a. 集合符号[] 内部可以是简单的字符、预定义类等大部分其他类构件。
例如可以写 [a-z] [\w]
b.虽然java的是基于NFA的,且基本等同于与per 5的实现,但是还有一些perl的语法不支持,如下(源于javadoc):
1) 向后引用构件 \g{n}
2)条件构件(?(condition)x)
3)内嵌代码构件 (?{
code})
and (??{
code})
,
4)内嵌注释构建(?#comment)
,
5)预处理操作:\l
\u
, \L
, and \U
.
从个人来看,和perl的最大区别就在于对于向后引用的支持不如perl那么方便,当然也有好的一面。
六、例子
6.1Pattern的函数实现查找和替换appendReplacement等
javadoc例子
/** * 测试find和appendReplacement * 持续查找某个模式,并替换为指定内容 appendReplacement */ public void contineFindP_and_replace(String functionName) { printHeader(functionName); Pattern p = Pattern.compile("\\$\\{[a-z0-9]+\\}"); Matcher m = p.matcher("one ${cat} ${two} cats in the yard"); StringBuffer sb = new StringBuffer(); while (m.find()) { m.appendReplacement(sb, "dog"); } m.appendTail(sb); System.out.println(sb.toString()); //注意以上代码等同于 m.replaceAll("dog"); }
输出如下:
---contineFindP_and_replace---------------------------------
one dog dog cats in the yard
6.2Pattern的函数全替换replaceAll
/** * 利用replaceAll函数直接替换为某个常量 * @param functionName */ public void replaceAllByMather(String functionName) { printHeader(functionName); Pattern p = Pattern.compile("\\$\\{\\D[a-z0-9\\_]*\\}"); // 可以找到2个 Matcher m = p.matcher("one ${c_13a6t} ${t3455_5w_o} cats in the yard"); String result = m.replaceAll("桃子"); System.out.println(result); }
---replaceAll-----------------------------------------------
one 桃子 桃子 cats in the yard
6.3Pattern的函数实现切割split
/** * 测试正则的其它函数-- Pattern的其它方法 */ public void testOhterFunction(String functionName) { printHeader(functionName); //匹配 前后同一个字符中间两个字符的串,如 ecce,abba String txt="luck and face ecce xccx pin"; String regExp="([a-z])\\1"; Pattern p = Pattern.compile(regExp); System.out.println("\""+txt+"\" found:"); //可以基于一个正则进行切割 String[] arr=p.split(txt); int no=0; for(String part:arr) { no++; System.out.println(no+":"+part); } //把a变成 \Qa\E System.out.println(Pattern.quote("a")); String txt1="SSS \\Qa\\E "; String regExp1="\\Q[a-z]+\\E"; //p右边有数字的不要 Pattern p1 = Pattern.compile(regExp1); Matcher m1 = p1.matcher(txt1); while (m1.find()) { String found = m1.group(); System.out.println(txt1+" found("+regExp1+"):" +m1.groupCount() +":" + found); } }
---Pattern其它函数----------------------------------------------
"luck and face ecce xccx pin" found:
1:luck and face e
2:e x
3:x pin
\Qa\E
6.4预定义字符类
/** * 测试预定义类 */ public void testPredefineClassAndCollection(String functionName) { printHeader(functionName); String txt="$-- this is good gs83883sdsd 99♡99 99 88♡♡88" + "$-- s"; String regExp= "(^\\$[\\-]*)|" //以$开头,后跟-- + "([\\w]+[\\d]+[\\w]+)|" //中间数字,两边字母 + "\s*s$|" //以s结尾的 + "[\\d]{2,}[♡]?+[\\d]{2,}"; //左右必须各有至少2各个或者2个以上字母,中间有一个♡或者没有♡, Pattern p = Pattern.compile(regExp); Matcher m = p.matcher(txt); while (m.find()) { String found = m.group(); System.out.println("found:" + found); } }
---预定义字符类---------------------------------------------------
found:$--
found:gs83883sdsd
found:99♡99
found: s
6.5回溯/向后引用
/** * 测试向后引用 \n,可用于测试左右对称的情况 */ public void testBackReference(String functionName) { printHeader(functionName); //匹配 前后同一个字符中间两个字符的串,如 ecce,abba String txt="aa bb cc dd abab ecce xccx"; String regExp="([a-z])([a-z])\\2\\1"; Pattern p = Pattern.compile(regExp); Matcher m = p.matcher(txt); while (m.find()) { String found = m.group(); System.out.println("found:" + found); } }
---回溯-------------------------------------------------------
found:ecce
found:xccx
6.6特殊构件--绕口令
不好记,需要的时候翻看下。
/** * 测试一些特别的构件--即奇怪部分: 无效捕获,瞻前/向前看,顾后/向后看 * ! 不满足,但会捕获 * = 满足,但不捕获 */ public void testSpecialConstruct(String functionName) { printHeader(functionName); //!,没有的要 String txt=" p0nd pnd z0nd znd"; String regExp= "p(?!\\d+)nd|" //p右边没有数字的要捕获(found) + "z(?<!\\d+)nd"; //n左边没有数字的要捕获 Pattern p = Pattern.compile(regExp); Matcher m = p.matcher(txt); while (m.find()) { String found = m.group(); System.out.println("found:" + found); } //=,有的不要 String txt1=" xp0 xp "; String regExp1="xp(?=0)"; //p右边有数字的不要 Pattern p1 = Pattern.compile(regExp1); Matcher m1 = p1.matcher(txt1); while (m1.find()) { String found = m1.group(); System.out.println(txt1+" found("+regExp1+"):" +m1.groupCount() +":" + found); }
//<=,有的不要 String txt2=" 0xp xp "; String regExp2="(?<=0)xp"; //xp左边有数字的不要 Pattern p2 = Pattern.compile(regExp2); Matcher m2 = p2.matcher(txt2); while (m2.find()) { String found = m2.group(); System.out.println(txt2+" found("+regExp2+"):" +m2.groupCount() +":" + found); } //>,有的要,和方向没有关系 String txt3=" 0xp xp xp0"; String regExp3="(?>0)xp|xp(?>0)"; //有0的要 Pattern p3 = Pattern.compile(regExp3); Matcher m3 = p3.matcher(txt3); while (m3.find()) { String found = m3.group(); System.out.println(txt3+" found("+regExp3+"):" +m3.groupCount() +":" + found); } }
---特殊构件-----------------------------------------------------
found:pnd
found:znd
xp0 xp found(xp(?=0)):0:xp
0xp xp found((?<=0)xp):0:xp
0xp xp xp0 found((?>0)xp|xp(?>0)):0:0xp
0xp xp xp0 found((?>0)xp|xp(?>0)):0:xp0
6.7匹配中文
/** * 测试匹配汉字 * 需要注意的是</br> * 1.java代码常常是UTF8格式保存的</br> * 2.UTF-16编码不同于utf8编码。前者两个字节表示一个汉字,后者是3个字节表示一个汉字</br> * @throws UnsupportedEncodingException */ public void testMatchChinese(String functionName) throws UnsupportedEncodingException{ printHeader(functionName); //匹配UTF8的汉字 String txt="aa 卢 cc 中 aba不要瞻前顾后"; String regExp="[\\u4e00-\\u9fa5]"; Pattern p = Pattern.compile(regExp); Pattern.compile(regExp, 0); Matcher m = p.matcher(txt); while (m.find()) { String found = m.group(); System.out.println("found:" + found); } //匹配UNICODE汉字(utf-16) String txtUnicode=new String(txt.getBytes("UTF-16"),"UTF-16"); regExp="[\\u4e00-\\u9fa5]"; Pattern p1 = Pattern.compile(regExp); Matcher m1 = p1.matcher(txtUnicode); while (m1.find()) { String found = m1.group(); System.out.println("found:" + found); } }
---匹配中文-----------------------------------------------------
found:卢
found:中
found:不
found:要
found:瞻
found:前
found:顾
found:后
found:卢
found:中
found:不
found:要
found:瞻
found:前
found:顾
found:后
这说明,java自己会处理变编码。
不过以上仅仅是常规的汉字。
6.8解析函数表达式
来一些复杂的例子!
有的时候,我们需要在业务中允许使用自定义的函数,而且函数是带有不定参数的。
/**
* 测试稍微复杂的函数表达式以及逻辑操作符号|</br>
* 一个函数的参数列表,用逗号分割。</br>
* 分割的主要依据是逗号必须不是被双引号所包围的。 例如 1,",ssd",good 只能分割为
* 1和",ssd",good.
*/
public void testFunAndParam(String functionName) {
printHeader(functionName);
/**
* 5个分组 组: 1-函数名称 2-参数和括弧 3-做括弧 4-参数 5-右括弧
*/
String patternStr =
"(f_[a-z0-9\\_]*)"
+"("
+"(\\()"
+"(([0-9\\.\\-]*)(\\,)*(\"[^\"\\,]*\")*(\"[^\"]*\\,[^\"]*\")*(\\$\\{[a-z][0-9a-z_]*[0-9a-z_&&[^_]]\\})*)*"
+"(\\))"
+")";
Pattern p = Pattern.compile(patternStr);
Matcher m = p.matcher("one f_gogo f_sd33_33_nb_ f_cal(1,\"abc\"),3),"
+ "f_do(\"adfb\",\"dfdf\",\"sss\",12.4) "
+ "f_avg(1,2) f_cat(\"a\",\"c\") "
+ "f_ceil(12,${sddd_ee}) "
+ "f_ggg(\"abcde1,3f\") cats in the yard");
while (m.find()) {
String found = m.group();
System.out.println("found:" + found);
for (int i = 1, len = m.groupCount(); i <= len; i++) { //打印每个参数
System.out.println(i + ": " + m.group(i));
}
}
}
---函数表达式----------------------------------------------------
found:f_cal(1,"abc")
1: f_cal
2: (1,"abc")
3: (
4:
5:
6: ,
7: "abc"
8: null
9: null
10: )
found:f_do("adfb","dfdf","sss",12.4)
1: f_do
2: ("adfb","dfdf","sss",12.4)
3: (
4:
5:
6: ,
7: "sss"
8: null
9: null
10: )
注:内容太长,只列出部分结果。
6.9解释函数参数列表
/** * 测试分析函数表达式中参数表达式部分,并打印每个参数 */ public void testSplitParamExp(String functionName) { printHeader(functionName); /** * 参数表达式以逗号分割 * */ String[] expList = { "\"a\",12,0", "\"a\"", "\"a\",\"c\"", "1,2,3,4.0,5.0,0.34", "1,2,-3,4.0,-5.0,0.34", "1,\"ab,c\"", "1,\"abc\",\"我们,大家都考60.0分以上\"", "1,\"abc\",33333,${go_sd},${veee_384},12.99" }; String patternStr = "[0-9\\.\\-]+|" //数字 + "\"[^\"\\,]*\"|" //字符串 + "\"[^\"]*\\,[^\"]*\"|" //字符串中带有逗号的 + "\\$\\{\\D[a-z0-9\\_]*[^_]\\}"; //其它ETL参数 Pattern p = Pattern.compile(patternStr); for (int i = 0, len = expList.length; i < len; i++) { String exp = expList[i]; System.out.println("---------------------------------------"); System.out.println(exp); System.out.println("---------------------------------------"); Matcher m = p.matcher(expList[i]); while (m.find()) { String found = m.group(); System.out.println("found:" + found); } System.out.println(""); } }
---函数参数表达式--------------------------------------------------
---------------------------------------
"a",12,0
---------------------------------------
found:"a"
found:12
found:0
---------------------------------------
"a"
---------------------------------------
found:"a"
---------------------------------------
"a","c"
---------------------------------------
found:"a"
found:"c"
---------------------------------------
1,2,3,4.0,5.0,0.34
---------------------------------------
found:1
found:2
found:3
found:4.0
found:5.0
found:0.34
---------------------------------------
1,2,-3,4.0,-5.0,0.34
---------------------------------------
found:1
found:2
found:-3
found:4.0
found:-5.0
found:0.34
---------------------------------------
1,"ab,c"
---------------------------------------
found:1
found:"ab,c"
---------------------------------------
1,"abc","我们,大家都考60.0分以上"
---------------------------------------
found:1
found:"abc"
found:"我们,大家都考60.0分以上"
---------------------------------------
1,"abc",33333,${go_sd},${veee_384},12.99
---------------------------------------
found:1
found:"abc"
found:33333
found:${go_sd}
found:${veee_384}
found:12.99
七、小结
1.正则表达式这个东西,简单的也很简单,复杂的也有,例如特殊的构件
2.java自身提供的函数基本能满足要求,不过方便性不够。例如回溯引用的替换并不是那么方便,虽然可以做到。
3.要完整掌握,多少还是需要了解来龙去脉
4.需要较多的练习,才能熟练