正则学习二三事

正则一直是我一大痛点,一直都想解决这个问题,但是奈何每次看到那么多符号就发蒙,所以就一直拖下去了。直到最近总是被别人问到如何在hql中使用rlike查询符合特定规则的字段,然后各种不会,结果被鄙视的一塌糊涂,无奈,这才静下心来慢慢研究。
以前看正则就是一堆符号,代表各个意思,如\d表示数字,\d+表示一个或者多个连续数字,单看每个规则都可以理解,除了组合,但是实际使用时真的很难组织到一起。归根结底还是因为对这些符号的理解不够深入。所以正则还是得多写,推荐一个在线练习的网站RegexGolf。好了,下面写写自己学习正则的一些总结吧,希望能够帮助到别人,也帮助自己总结总结。

正则的基础知识:

字面值

正则表达式由只代表自身的字面值和代表特定含义的元字符组成。 只代表自身的字面值指的是普通的字符,如abcde,特殊含义的元字符包括:

字符 含义
\ 反斜线字符
\0n 带有八进制值 0 的字符 n (0 <= n <= 7)
\0nn 带有八进制值 0 的字符 nn (0 <= n <= 7)
\0mnn 带有八进制值 0 的字符 mnn(0 <= m <= 3、0 <= n <= 7)
\xhh 带有十六进制值 0x 的字符 hh
\uhhhh 带有十六进制值 0x 的字符 hhhh
\t 制表符 (‘\u0009’)
\n 新行(换行)符 (‘\u000A’)
\r 回车符 (‘\u000D’)
\f 换页符 (‘\u000C’)
\a 报警 (bell) 符 (‘\u0007’)
\e 转义符 (‘\u001B’)
\cx 对应于 x 的控制符
字符类

字符类是字符在方括号中的集合。表示“找到集合里任意一个字符“。例如:

字符 含义
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了 a、b 或 c(否定)
[a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)
[a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](并集)
[a-z&&[def]] d、e 或 f(交集)
[a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](减去)
[a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](减去)

从上表可以看出[]里面可进行并集/交集/差集操作。对于字符范围是根据ASCII值的大小来的,例如[A-z]也是可以的,甚至能够匹配[,但是完全不建议如此使用,推荐使用的字符范围:[0-9]/[a-z]/[A-Z]。
字符类还有一些预定义的字符类:

字符 含义
. 任何字符(与行结束符可能匹配也可能不匹配)
\d 数字:[0-9]
\D 非数字: [^0-9]
\s 空白字符:[ \t\n\x0B\f\r]
\S 非空白字符:[^\s]
\w 单词字符:[a-zA-Z_0-9]
\W 非单词字符:[^\w]

使用上面的预定义字符类能够更加方便的表示字符范围。需要牢记。
注意: 区间是字符的区间,不是数字的区间。正则表达式[1-31]表示“找到一个1或一个2或一个3”,不是“找到一个从1到31的整数”。

乘法器
字符 含义
X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,恰好 n 次
X{n,} X,至少 n 次
X{n,m} X,至少 n 次,但是不超过 m 次

X可以使一个普通字面值,如a+:一个或多个a,也可以是一个字符类,如[abc]{2},表示a/b/c后跟a/b/c。
值得注意的是优先选择更长的匹配,因为乘法器是贪婪的。如果你输入的文本是I had an aaaaawful day,该正则表达式就会在aaaaawful中匹配到aaaaa。不会在第三个a后就停止匹配。
乘法器是贪婪的,但它不会忽略一个更好的匹配。如果你的输入文本为I had an aaawful daaaaay,之后这个正则表达式会在第一次的匹配中于aaawful找到aaa。只有在你说“给我找到另一个匹配”的时候,它才会继续搜索然后在daaaaay中找到aaaaa。
惰性:
正则表达式“.表示“找到一个双引号,接着找到尽可能多的字符,最后再找到一个双引号”。注意一下被.匹配的内部字符,很可能包含多个双引号。这通常不是非常有用。乘法器可通过追加问号(?)来实现惰性“.?”*表示“匹配一个双引号,跟着一个尽可能少的字符,再跟着一个双引号”。

分支

可以使用管道符号来实现匹配多种选择。

字符 含义
X!Y(用!代替竖线) X或者Y
组合

可以使用圆括号来组合表达式。例:
在一周中找到一天,使用(Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day,这里如果把小括号或者中括号,结果是完全不一样的,因为中括号是字符类,即里面的Mon并不是完全匹配Mon,而是只要匹配M/o/n其中一个即可。
同时组合后面还可跟上乘法器,如:\w+(\s+\w+)*代表“找到一个或多个单词,它们以空格隔开”。

边界

边界分成:单词边界,行边界,文本边界

  1. 单词边界
    单词边界是一个单词字符和非单词字符之间的位置。记住,一个单词字符是\w,它是[0-9A-Za-z],一个非单词字符是\W,也就是[^0-9A-Za-z]。
    文本的开头和结尾总是当作单词边界。
    输入的文本it’s a cat有八个单词边界,分别为:文本开头-i,t-‘,’-s,s-空格,空格-a,a-空格,空格-c,t-文本结尾。
  2. 行边界
    每一块文本会分解成一个或多个行,用换行符分隔。
    注意文本不是以换行符结束,而是以行结束。然而,任何行,包括最后一行,可以包含零个字符。
    起始行位置是在一个换行符和下一行的第一个字符之间。与单词边界一样,在文本的开头也算作一个起始的行。结束行位置是在行的最后一个字符和换行符之间。与单词边界一样,文本结束也算作行结束。
  3. 文本边界
    很多实现提供一个标记,通过改变它来改变^和$的含义。从“行开始”和“行结束”变成“文本开始”和“文本结束”。其它的一些实现提供单独的元字符\A和\z来达到这个目的。
    一些表示边界的符号:
字符 含义
^ 行的开头
$ 行的结尾
\b 单词边界
\B 非单词边界
\A 输入的开头
\G 上一个匹配的结尾
\Z 输入的结尾,仅用于最后的结束符(如果有的话)
\z 输入的结尾

其中^$是最常用的两个边界分隔符。

捕获和替换:

  1. 捕获组

    ()在正则中被用来表示组,同时也可以用来捕获匹配上的子串,可以拥有多个捕获组,它们甚至可以嵌套使用,捕获组从左到右进行编号,只要计算左圆括号。例如:
    对于表达式:(\w+) had a ((\w+) \w+),文本是I had a nice day,那么

    • 捕获组1是I。
    • 捕获组2是nice day。
    • 捕获组3是nice。
    • 捕获组0是I had a nice day(根据具体实现不同)

    如果表达式使用了两个捕获组,但是只捕获到一组,那么组2是空字符串。引用捕获组使用+组序号,如\1表示引用第一个捕获组。

  2. 后向引用
    可以在同样的表达式中引用同一个捕获组,这称为后向引用。
    例:表达式[abc]{2}表示“匹配aa或ab或ac or ba或bb或bc或ca或cb或cc”,但是表达式([abc])\1表示“匹配aa或bb或cc”。

以上就是正则的全部知识,其实了解正则的知识点很简单,但是真要应用到实际中还是需要通过大量的练习才能做到熟练使用。

实际案例
  1. 压缩CSS文件,去掉CSS文件中的换行以及空格

    1
    工具:Notepad++  查找:([{;])\s+  替换:\1
  2. 替换文件中连续出现的#,为其后面添加一个空格,例:####你好->#### 你好

    1
    工具:Notepad++  查找:([#]+)  替换:\1 (1后面有个空格)

以上案例会不断更新,已记录一些自己对正则使用的经历。

参考文章

55分钟学会正则表达式(译)

JDK API文档