正则表达式是开发人员处理文本的好选择,在不同的语音之间有一定的共通性,也是一个开发人员必备的基础知识之一,在此特结合人民邮电出版社的《正则表达式必知必会》一书
就按照《正则表达式必知必会》,以下简称《正必》一书的章节目录进行实践。
我使用的java版本是1.8.0_65,使用的编译器是eclipse 2018-09,使用的是java.util.regex包下的正则表达式匹配方法。
第二章:
(1)首先是用字符串匹配,按照《正必》一书中的匹配法则,java中对应的方法是contains(CharSequence s),这个方法在java.lang.String类中。
(2)关于大小写的问题,在java.lang.String类中toLowerCase(),toUpperCase()就可以实现对字符串的大小写转换。
(3)接下来就是普通正则表达式的使用了,使用Pattern的matches方法,匹配则返回true,反之则返回false。《正必》第9页的实践如下,想匹配“任意字符a任意字符”的格式。因为后面有后缀xls,因此原书的方法在java并不适用。在java中使用“.a.”只能匹配只含有三个字符且中间字符为a的字符串。想要匹配例如“na4.xls”字符串,则要在“.a.”之后添加对“.xls”的匹配。当然,如果只是想匹配某个字符串的子串是否存在,比如我想匹配“任意字符a任意字符”的格式,那么正则表达式应该这样写:".*.a..*"才可以匹配“任意字符a任意字符”。这里的任意字符不能为空,也不能为换行字符。
(4)特殊字符的匹配,对于特殊字符来说,只要在其前加\就可以将其视为一个普通字符,而java中\\表示的是一个具有特殊含义的字符\,而\\\\才表示一个普通字符\。
第三章:
(1)匹配多个字符中的一个,用中括号括起来就好了,比如我要匹配“na任意字符”和“sa任意字符”,只需要将正则表达式写成“[ns]a.”就可以了。
(2)字符区间匹配,比如我要匹配任意小写字母,我难道要用中括号括起来26个字母么?当然不需要,只需要写成“[a-z]”就行了,同理,我要匹配a-f,只要写成“[a-f]”就行了,如果有多个区间,比如忽略大小写匹配所有字母,就可以写成“[a-zA-Z]”。这个区间是指ASCII码字符表里的顺序,具体可以参考ASCII码表。
(3)取非匹配,参考(2)在前面添加字符‘^’即可。比如我某一位上要匹配非数字,那么就可以写成“[^0-9]”。要注意的是,‘^’在区间内出现,意味着对区间内的所有取非。
第四章:
(1)对特殊字符转义,这个前面已经说过了。
(2)匹配空白字符,空白字符一共有多种,最常见的就是回车‘\r’,换行符‘\n’和制表符‘\t’。对于Windows系统来说,一行的结尾是回车换行,也就是“\r\n”,对于linux和Unix来说就是“\n”。
(3)匹配特别的字符类别。匹配数字:\d,非数字:\D;匹配字母数字下划线(等价于“[a-zA-Z0-9_]”):\w,非字母数字下划线:\W;匹配空白字符(等价于“[\f\n\r\t\v]}”):\s,非空白字符:\S。
(4)POSIX字符。java语言的POSIX字符表示如下,以\p开头,然后用大括号括起来特定的单词用来表示不同的匹配规则。小写字母:\p{Lower};大写字母:\p{Upper};ASCII字符:\p{ASCII};字母字符:\p{Alpha};十进制数字:\p{Digit};字母数字字符:\p{Alnum};标点符号(!”#$%&’()*+,-./:;<=>?@[]^_>{Ι}< ):\p{Punct};一个可视的字符:\p{Graph};可打印字符:\p{Print};空格或制表符([ \t]
):\p{Blank};十六进制数字:\p{XDigit};空白字符([ \t\n\x0B\f\r]):\p{Space}。当然,在java语言中表示一个有特殊意义的\需要用\\来表示,在写匹配的字符串时,尤其要注意。
第五章:
(1)匹配一个或多个字符,只需要在满足需求的表达式后面添加一个‘+’,就可以实现匹配一或多个字符
(2)如果要匹配0个或者多个字符,那么就在满足需求的表达式后面添加一个‘*’,就可以实现了
(3)那么如果要匹配零个或一个字符,只需要‘?’来匹配就可以了
(4)如果要指定匹配某个字符的个数呢?那么只要在表达式之后用大括号括起来次数就可以了。比如要匹配5个连着的n,就可以写成“n{5}”。
(5)那么如果要匹配某个字符重复次数在一个区间内,那么和(4)相似,大括号括起来起始数量,比如匹配2--5个n,就可以写成“n{2,5}”。
(6)如果是要匹配至少重复某个字符多少次,类似的,只需要在5的情况下省略最大值就可以了,比如匹配5个或者5个以上的n,就可以写成“n{5,}”。
(7)过度匹配,正则表达式匹配有两种模式,贪婪型和懒惰型。前者要从开始开始一直匹配到文本末尾,后者则是在完成第一次匹配后就会停止工作。我们前面提及的‘+’,‘*’,‘{2, }’都是贪婪型的。如果想要将其变为懒惰型的,那么只要在其后加上‘?’即可。
第六章
(1)单词边界,使用‘\b’来表示单词边界。这个单词边界是什么意思呢,这有个小例子来体现。对文本“The cat scattered his food all over the room.”按照‘\b’的正则表达式作为分隔符来分割文本并进行输出。输出结果如下:
可以很明显的看出来,每个单词,包括空格和标点符号,都被分割开来,也就是说文本中蕴含了我们无法看到的分割符。当我们想匹配这个文本中的cat时,我们也会匹配到含有cat的scattered。当我们使用“\bcat\b”作为匹配表达式时,就之后匹配cat单词而不会匹配到含有cat的单词了。当然,按照前面的规则,使用‘\B’就可以不匹配单词边界。
(2)字符串边界:‘^’标注字符串的开始,‘$’标注字符串的结束。如果是多行的情况,只需要在匹配表达式前加‘(?m)’或者使用如下代码:
Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
第七章:
(1)如果我们要匹配一个重复出现的字符串,那么我们就可以用小括号括起来这字符串,其他处理就和之前所说相同了。用小括号括起来的表达式被称为子串。
(2)子串是允许无限循环嵌套的,但是过多的嵌套很容易带来两个问题,一是可读性问题,二是性能问题。
第八章:
(1)回溯引用,比如我们在html语言中的标题<h1>标题1</h1>,我们按照之前所说可以很容易的匹配到。但是如果代码中有错误的标题,比如“标题<h2>标题2</h3>”,这个是错误的,我们不能把他匹配到。所以我们要选用回溯引用,保证前后一致。这也要用到之前所说的子串。如过要匹配这种模式,就可以这样写:“<[Hh]([0-9])>.*?</[Hh]\\1>”。这是在java中的写法,\\1表示为\1,也就是第一个子串。
(2)回溯引用在替换操作中很有用,但是过于简单的替换用正则表达式有一些小题大做,但是对于一些比较复杂的替换,比如对某些文字的排版,等某部分的单词进行大小写变换等等。
第九章:
(1)向前查找:譬如我们想提取每一行冒号之前的字符串,我们可以这样写:“.+(?=:)”。如果是否定性向前查找,就把‘=’替换为‘!’即可。
(2)向后查找:“.+(?<=:)”,否定性,就把‘=’替换为‘!’即可。
第十章:
(1)正则表达式里的条件,语法是:“?(backerference)true-regex|false-regex)”。也就是给定一个条件backerference,满足条件与否将会分别执行true-regex和false-regex表达式。这种模式很复杂,因此调试起来很困难,可读性也很差。
(2)前后查找条件,例如匹配一个字符串为“五位数字-四位数字”,可以这样写:“\d{5}(?(?=-))-\d{4})”。此处的\在java语言中要写做\\。