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,10FFFF]

字符   \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  水平空格字符 [ \t\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]

\H 非水平空格字符,等同于[^h]

\s  空格字符:[\t\n\x0B\f\r]

\S 非空格字符

\v 垂直空格字符   [\n\x0B\f\r\x85\u2028\u2029]

\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.需要较多的练习,才能熟练

 

posted @ 2022-12-01 21:59  正在战斗中  阅读(517)  评论(0编辑  收藏  举报