前言:最近在公司做了一些关于网页抓取的事情,其中有不少用到正则表达式的地方,自己记忆力太差,很久不用之后,正则表达式及其使用(这里讲解的是java中的正则表达式的使用),就忘得差不多了,不得不拿起书来又翻了一翻,温故而知新(之所以忘也是因为了解的不深了)。
1.正则表达式
1.1 由来
这回百度了一下的正则表达式的历史,大概就是说正则表达式的鼻祖是研究神经生理方面的两位科学家:
美国新泽西州的Warren McCulloch和出生在美国底特律的Walter Pitts,他们研究出了一种用数学方法来描述网络神经的新方法,发表了一片《网络神经表示法》的论文,其中引用的数学符号集合他们称其为正则(Regex),数学符号组成的表示方式就自然叫做正则表达式。貌似扯远了,之后,大名鼎鼎的Unix之父Ken Thompson就发现,咦,这玩意儿貌似还挺好用的,就毫不客气的将其引入到了计算机领域,最初用在了一个编辑器qed上面,之后引入Unix上的ed编译器,还有grep(应该没有人对该命令陌生吧)。之后perl成功了,据说也是由于正则表达式的功劳,然后各种语言都支持正则表达式了,今天要讲的java当然也不例外。然后的然后,正则表达式就演化称为计算机技术森林中的一只形神美丽且声音动听的百灵鸟(词句来着百度)。
1.2 概念
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符,以及这些字符的组合,组成一个“规则字符串”,这个“规则字符串”用来匹配这些规则的字符串,也就是用来表达对字符串的一种过滤逻辑。
通过正则表达式,1. 可以匹配某个字符串。2.从字符串中获取我们想要的部分。能够解决各种字符串处理相关的问题:匹配,选择,编辑以及验证。
1.3 Java正则表达式基础
前面的定义中已经说了,正则表达式是用实现定义好的一个集合来描述字符串的,我们就先来看看在java中这组集合是如何被定义的。以下为jdk6的Pattern类中定义的正则集合。
构造 | 匹配 |
---|---|
字符 | |
x | 字符 x |
\\ | 反斜线字符 |
\0 n | 带有八进制值 0 的字符 n (0 <= n <= 7) |
\0 nn | 带有八进制值 0 的字符 nn (0 <= n <= 7) |
\0 mnn | 带有八进制值 0 的字符 mnn(0 <= m <= 3、0 <= n <= 7) |
\x hh | 带有十六进制值 0x 的字符 hh |
\u hhhh | 带有十六进制值 0x 的字符 hhhh |
\t | 制表符 ('\u0009' ) |
\n | 新行(换行)符 ('\u000A' ) |
\r | 回车符 ('\u000D' ) |
\f | 换页符 ('\u000C' ) |
\a | 报警 (bell) 符 ('\u0007' ) |
\e | 转义符 ('\u001B' ) |
\c x | 对应于 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] (减去) |
预定义字符类 | |
. | 任何字符(与行结束符可能匹配也可能不匹配) |
\d | 数字:[0-9] |
\D | 非数字: [^0-9] |
\s | 空白字符:[ \t\n\x0B\f\r] |
\S | 非空白字符:[^\s] |
\w | 单词字符:[a-zA-Z_0-9] |
\W | 非单词字符:[^\w] |
POSIX 字符类(仅 US-ASCII) | |
\p{Lower} | 小写字母字符:[a-z] |
\p{Upper} | 大写字母字符:[A-Z] |
\p{ASCII} | 所有 ASCII:[\x00-\x7F] |
\p{Alpha} | 字母字符:[\p{Lower}\p{Upper}] |
\p{Digit} | 十进制数字:[0-9] |
\p{Alnum} | 字母数字字符:[\p{Alpha}\p{Digit}] |
\p{Punct} | 标点符号:!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ |
\p{Graph} | 可见字符:[\p{Alnum}\p{Punct}] |
\p{Print} | 可打印字符:[\p{Graph}\x20] |
\p{Blank} | 空格或制表符:[ \t] |
\p{Cntrl} | 控制字符:[\x00-\x1F\x7F] |
\p{XDigit} | 十六进制数字:[0-9a-fA-F] |
\p{Space} | 空白字符:[ \t\n\x0B\f\r] |
java.lang.Character 类(简单的Java字符类型) | |
\p{javaLowerCase} | 等效于 java.lang.Character.isLowerCase() |
\p{javaUpperCase} | 等效于 java.lang.Character.isUpperCase() |
\p{javaWhitespace} | 等效于 java.lang.Character.isWhitespace() |
\p{javaMirrored} | 等效于 java.lang.Character.isMirrored() |
Unicode 块和类别的类 | |
\p{InGreek} | Greek 块(简单块)中的字符 |
\p{Lu} | 大写字母(简单类别) |
\p{Sc} | 货币符号 |
\P{InGreek} | 所有字符,Greek 块中的除外(否定) |
[\p{L}&&[^\p{Lu}]] | 所有字母,大写字母除外(减去) |
边界匹配器 | |
^ | 行的开头 |
$ | 行的结尾 |
\b | 单词边界 |
\B | 非单词边界 |
\A | 输入的开头 |
\G | 上一个匹配的结尾 |
\Z | 输入的结尾,仅用于最后的结束符(如果有的话) |
\z | 输入的结尾 |
Greedy 数量词 | |
X? | X,一次或一次也没有 |
X* | X,零次或多次 |
X+ | X,一次或多次 |
X{ n} | X,恰好 n 次 |
X{ n,} | X,至少 n 次 |
X{ n, m} | X,至少 n 次,但是不超过 m 次 |
Reluctant 数量词 | |
X?? | X,一次或一次也没有 |
X*? | X,零次或多次 |
X+? | X,一次或多次 |
X{ n}? | X,恰好 n 次 |
X{ n,}? | X,至少 n 次 |
X{ n, m}? | X,至少 n 次,但是不超过 m 次 |
Possessive 数量词 | |
X?+ | X,一次或一次也没有 |
X*+ | X,零次或多次 |
X++ | X,一次或多次 |
X{ n}+ | X,恰好 n 次 |
X{ n,}+ | X,至少 n 次 |
X{ n, m}+ | X,至少 n 次,但是不超过 m 次 |
Logical 运算符 | |
XY | X 后跟 Y |
X| Y | X 或 Y |
( X) | X,作为捕获组 |
Back 引用 | |
\ n | 任何匹配的 nth 捕获组 |
引用 | |
\ | Nothing,但是引用以下字符 |
\Q | Nothing,但是引用所有字符,直到 \E |
\E | Nothing,但是结束从 \Q 开始的引用 |
特殊构造(非捕获) | |
(?: X) | X,作为非捕获组 |
(?idmsux-idmsux) | Nothing,但是将匹配标志 i d m s u x on - off |
(?idmsux-idmsux: X) | X,作为带有给定标志 i d m s u x on - off |
(?= X) | X,通过零宽度的正 lookahead |
(?! X) | X,通过零宽度的负 lookahead |
(?<= X) | X,通过零宽度的正 lookbehind |
(?<! X) | X,通过零宽度的负 lookbehind |
(?> X) | X,作为独立的非捕获组 |
咋一看,确实挺多的,不过我们常用的也不多。以下分别介绍部分。在介绍之前我们先讲一下Java中对于反斜线的使用。
从上面的定义中,反斜线用于转义构造同时还由于其他非转义构造。例如\\表示反斜线,\{表示左括号匹配
但是在Java中,由于编译器在解释Java源代码中的反斜线的时候将反斜线解释为Unicode转义或者其他字符转义。因此必须在字符串字面值中使用两个反斜杠,才能表示正则表达式。
因此 \w表示为\\w,如果要匹配一个反斜杠的话,那么就要用 \\\\ 来表示。而换行符\n等由于Java解释器本身就将其解释为反斜杠,所以说要匹配换行符是不需要做任何特殊处理的。
字符类
字符类可以出现在其他字符类中,并且可以包含并集运算符(隐式)和交集运算符 (&&)。并集运算符表示至少包含其某个操作数类中所有字符的类。交集运算符表示包含同时位于其两个操作数类中所有字符的类。
字符类运算符的优先级如下所示,按从最高到最低的顺序排列:
1 字面值转义 \x2 分组 [...]3 范围 a-z4 并集 [a-e][i-u]5 交集 [a-z&&[aeiou]]
注意,元字符的不同集合实际上位于字符类的内部,而非字符类的外部。例如,正则表达式 . 在字符类内部就失去了其特殊意义,而表达式 - 变成了形成元字符的范围。
行结束符
行结束符 是一个或两个字符的序列,标记输入字符序列的行结尾。以下代码被识别为行结束符:
新行(换行)符 ('\n')、后面紧跟新行符的回车符 ("\r\n")、单独的回车符 ('\r')、下一行字符 ('\u0085')、行分隔符 ('\u2028') 或段落分隔符 ('\u2029)。
如果激活 UNIX_LINES 模式,则新行符是唯一识别的行结束符。
如果未指定 DOTALL 标志,则正则表达式 . 可以与任何字符(行结束符除外)匹配。
默认情况下,正则表达式 ^ 和 $ 忽略行结束符,仅分别与整个输入序列的开头和结尾匹配。如果激活 MULTILINE 模式,则 ^ 在输入的开头和行结束符之后(输入的结尾)才发生匹配。处于 MULTILINE 模式中时,$ 仅在行结束符之前或输入序列的结尾处匹配。
组和捕获
捕获组可以通过从左到右计算其开括号来编号。例如,在表达式 ((A)(B(C))) 中,存在四个这样的组:
1 ((A)(B(C)))2 (A)3 (B(C))4 (C)
组零始终代表整个表达式。
之所以这样命名捕获组是因为在匹配中,保存了与这些组匹配的输入序列的每个子序列。捕获的子序列稍后可以通过 Back 引用在表达式中使用,也可以在匹配操作完成后从匹配器获取。
与组关联的捕获输入始终是与组最近匹配的子序列。如果由于量化的缘故再次计算了组,则在第二次计算失败时将保留其以前捕获的值(如果有的话)例如,将字符串 "aba" 与表达式 (a(b)?)+ 相匹配,会将第二组设置为 "b"。在每个匹配的开头,所有捕获的输入都会被丢弃。
以 (?) 开头的组是纯的非捕获 组,它不捕获文本,也不针对组合计进行计数。
1.4 Java中正则表达式的使用。
1.4.1 String 类
Java中应用正则表达式的最简单的途径就是利用String类内建的功能。如matches函数,split函数,replaceFirst函数,replaceAll函数。如:"-1234".mathces("-?\\d+"), "Hello world".split("\\W")等。当然这些都是一些简单的应用。更强大的使用方法在下面。
1.4.2 Pattern类和Matcher类
只需要引入java.util.regex包我们就可以使用Pattern和Matcher这两个类了。最简单的使用方法如下所示:
import java.util.regex.Matcher;其中我们只要将正则表达式传入Pattern.complie()就可以获得一个编译后的Pattern对象。之后利用pattern对象的matcher方法,将要匹配的字符串传入方法中,就可以得到一个Matcher对象,利用Matcher对象就可以根据需要对正则表达式的匹配做相应的操作了。Matcher对象有很多很有用的方法,一下是查看是否匹配成功的函数
import java.util.regex.Pattern;
/**
* This is a test class
* Created by Administrator on 2015/3/14 0014.
*/
public class Test {
public static void main(String[] args) {
String input = "dabcabcabcsdaferfdabcabcsafgtre";
String expression = "(abc)+";
Pattern pattern = Pattern.compile(expression);
Matcher matcher = pattern.matcher(input);
while (matcher.find()){
System.out.println(matcher.group());
}
}
}
boolean matches() 用来判断整个字符串是否匹配成功匹配正则表达式
boolean lookingAt() 用来查看字符串的开始部分是否匹配成功(不需要是整个字符串)
boolean find() 像迭代器一样遍历整个字符串,找到后返回,在迭代
boolean find(int start) 与find一样不过,是从start位子开始迭代的。
组的概念:还记得我们在第三节介绍的捕获组的概念吗?在这里总算是排上用场了,示例代码中的group默认的是group(0) ,当然你也可以获取不同的group的值。还是那个例子:
对于:((A)(B(C)))
matcher.group() 表示((A)(B(C)))
matcher.group(1) ((A)(B(C)))
matcher.group(2) (A)
matcher.group(3) (B(C))
matcher.group(4) (C)
Pattern的compile函数还有一个重载版本
Pattern.complie(String regex,int flag),其中flag来自Pattern类中的常量:如:Pattern.CANON_EQ,Pattern.CASE_INSENSITIVE(?!)等,不同的常量主要用于满足不同的效果。有兴趣的读者可以参考一下源码查看不同flag的不同效果。
另外matcher还有很多其他的实用操作:
split(CharSequence input) 用于将字符串分割成字符串数组
split(CharSequence input,int limit) 用于将字符串分割成字符串数组,最少limit个
reset(String input) 将现有的Matcher对象应用到一个新的字符串上面
replaceFirst(String replacement) 替换掉第一个匹配成功的部分
replaceAll(String replacement) 替换掉所有匹配成功的部分