原文地址 作者:jakob jenkov 译者:严亮
java 提供了功能强大的正则表达式api,在java.util.regex 包下。本教程介绍如何使用正则表达式api。
一个正则表达式是一个用于文本搜索的文本模式。换句话说,在文本中搜索出现的模式。例如,你可以用正则表达式搜索网页中的邮箱地址或超链接。
下面是一个简单的java正则表达式的例子,用于在文本中搜索 http://
<code>1</code>
<code>string text =</code>
<code>2</code>
<code> </code><code>"this is the text to be searched "</code> <code>+</code>
<code>3</code>
<code> </code><code>"for occurrences of the http:// pattern."</code><code>;</code>
<code>4</code>
<code>string pattern =</code><code>".*http://.*"</code><code>;</code>
<code>5</code>
<code>boolean</code> <code>matches = pattern.matches(pattern, text);</code>
<code>6</code>
<code>system.out.println(</code><code>"matches = "</code> <code>+ matches);</code>
示例代码实际上没有检测找到的 http:// 是否是一个合法超链接的一部分,如包含域名和后缀(.com,.net 等等)。代码只是简单的查找字符串 http:// 是否出现。
本教程介绍了java6 中关于正则表达式的api。
类 java.util.regex.pattern 简称 pattern, 是java正则表达式api中的主要入口,无论何时,需要使用正则表达式,从pattern 类开始
检查一个正则表达式的模式是否匹配一段文本的最直接方法是调用静态方法pattern.matches(),示例如下:
<code> </code><code>"for occurrences of the pattern."</code><code>;</code>
<code>string pattern =</code><code>".*is.*"</code><code>;</code>
上面代码在变量 text 中查找单词 “is” 是否出现,允许”is” 前后包含 0或多个字符(由 .* 指定)
pattern.matches() 方法适用于检查 一个模式在一个文本中出现一次的情况,或适用于pattern类的默认设置。
如果需要匹配多次出现,甚至输出不同的匹配文本,或者只是需要非默认设置。需要通过pattern.compile() 方法得到一个pattern 实例。
如果需要匹配一个正则表达式在文本中多次出现,需要通过pattern.compile() 方法创建一个pattern对象。示例如下
<code>string patternstring =</code><code>".*http://.*"</code><code>;</code>
<code>pattern pattern = pattern.compile(patternstring);</code>
可以在compile 方法中,指定一个特殊标志:
<code>pattern pattern = pattern.compile(patternstring, pattern.case_insensitive);</code>
pattern 类包含多个标志(int 类型),这些标志可以控制pattern 匹配模式的方式。上面代码中的标志使模式匹配是忽略大小写
一旦获得了pattern对象,接着可以获得matcher对象。matcher 示例用于匹配文本中的模式.示例如下
<code>matcher matcher = pattern.matcher(text);</code>
matcher类有一个matches()方法,可以检查文本是否匹配模式。以下是关于matcher的一个完整例子
<code>7</code>
<code>boolean</code> <code>matches = matcher.matches();</code>
<code>8</code>
pattern 类的 split()方法,可以用正则表达式作为分隔符,把文本分割为string类型的数组。示例:
<code>string text =</code><code>"a sep text sep with sep many sep separators"</code><code>;</code>
<code>string patternstring =</code><code>"sep"</code><code>;</code>
<code>string[] split = pattern.split(text);</code>
<code>system.out.println(</code><code>"split.length = "</code> <code>+ split.length);</code>
<code>for</code><code>(string element : split){</code>
<code> </code><code>system.out.println(</code><code>"element = "</code> <code>+ element);</code>
<code>}</code>
上例中把text 文本分割为一个包含5个字符串的数组。
pattern 类的 pattern 返回用于创建pattern 对象的正则表达式,示例:
<code>string pattern2 = pattern.pattern();</code>
上面代码中 pattern2 值为sep ,与patternstring 变量相同。
java.util.regex.matcher 类用于匹配一段文本中多次出现一个正则表达式,matcher 也适用于多文本中匹配同一个正则表达式。
matcher 有很多有用的方法,详细请参考官方javadoc。这里只介绍核心方法。
以下代码演示如何使用matcher
首先创建一个pattern,然后得到matcher ,调用matches() 方法,返回true 表示模式匹配,返回false表示不匹配。
可以用matcher 做更多的事。
通过pattern 的matcher() 方法创建一个matcher。
matcher 类的 matches() 方法用于在文本中匹配正则表达式
如果文本匹配正则表达式,matches() 方法返回true。否则返回false。
matches() 方法不能用于查找正则表达式多次出现。如果需要,请使用find(), start() 和 end() 方法。
lookingat() 与matches() 方法类似,最大的不同是,lookingat()方法对文本的开头匹配正则表达式;而
matches() 对整个文本匹配正则表达式。换句话说,如果正则表达式匹配文本开头而不匹配整个文本,lookingat() 返回true,而matches() 返回false。 示例:
<code>string patternstring =</code><code>"this is the"</code><code>;</code>
<code>system.out.println(</code><code>"lookingat = "</code> <code>+ matcher.lookingat());</code>
<code>system.out.println(</code><code>"matches = "</code> <code>+ matcher.matches());</code>
上面的例子分别对文本开头和整个文本匹配正则表达式 “this is the”. 匹配文本开头的方法(lookingat()) 返回true。
对整个文本匹配正则表达式的方法 (matches()) 返回false,因为 整个文本包含多余的字符,而 正则表达式要求文本精确匹配”this is the”,前后又不能有额外字符。
find() 方法用于在文本中查找出现的正则表达式,文本是创建matcher时,通过 pattern.matcher(text) 方法传入的。如果在文本中多次匹配,find() 方法返回第一个,之后每次调用 find() 都会返回下一个。
start() 和 end() 返回每次匹配的字串在整个文本中的开始和结束位置。实际上, end() 返回的是字符串末尾的后一位,这样,可以在把 start() 和 end() 的返回值直接用在string.substring() 里。
<code>01</code>
<code>02</code>
<code> </code><code>"this is the text which is to be searched "</code> <code>+</code>
<code>03</code>
<code> </code><code>"for occurrences of the word 'is'."</code><code>;</code>
<code>04</code>
<code>string patternstring =</code><code>"is"</code><code>;</code>
<code>05</code>
<code>06</code>
<code>07</code>
<code>int</code> <code>count =</code><code>0</code><code>;</code>
<code>08</code>
<code>while</code><code>(matcher.find()) {</code>
<code>09</code>
<code> </code><code>count++;</code>
<code>10</code>
<code> </code><code>system.out.println(</code><code>"found: "</code> <code>+ count +</code><code>" : "</code> <code>+ matcher.start() +</code><code>" - "</code> <code>+ matcher.end());</code>
<code>11</code>
这个例子在文本中找到模式 “is” 4次,输出如下:
reset() 方法会重置matcher 内部的 匹配状态。当find() 方法开始匹配时,matcher 内部会记录截至当前查找的距离。调用 reset() 会重新从文本开头查找。
也可以调用 reset(charsequence) 方法. 这个方法重置matcher,同时把一个新的字符串作为参数传入,用于代替创建 matcher 的原始字符串。
假设想在一个文本中查找url链接,并且想把找到的链接提取出来。当然可以通过 start()和 end()方法完成。但是用group()方法更容易些。
分组在正则表达式中用括号表示,例如:
(john)
此正则表达式匹配john, 括号不属于要匹配的文本。括号定义了一个分组。当正则表达式匹配到文本后,可以访问分组内的部分。
使用group(int groupno) 方法访问一个分组。一个正则表达式可以有多个分组。每个分组由一对括号标记。想要访问正则表达式中某分组匹配的文本,可以把分组编号传入 group(int groupno)方法。
group(0) 表示整个正则表达式,要获得一个有括号标记的分组,分组编号应该从1开始计算。
<code>string text = </code><code>"john writes about this, and john writes about that,"</code> <code>+</code>
<code> </code><code>" and john writes about everything. "</code> <code>;</code>
<code>string patternstring1 =</code><code>"(john)"</code><code>;</code>
<code>pattern pattern = pattern.compile(patternstring1);</code>
<code> </code><code>system.out.println(</code><code>"found: "</code> <code>+ matcher.group(</code><code>1</code><code>));</code>
上面提到,一个正则表达式可以有多个分组,例如:
<code>(john) (.+?)</code>
这个表达式匹配文本”john” 后跟一个空格,然后跟1个或多个字符,最后跟一个空格。你可能看不到最后的空格。
这个表达式包括一些字符有特别意义。字符 点 . 表示任意字符。 字符 + 表示出现一个或多个,和. 在一起表示 任何字符,出现一次或多次。字符? 表示 匹配尽可能短的文本。
完整代码如下
<code> </code><code>"john writes about this, and john doe writes about that,"</code> <code>+</code>
<code> </code><code>" and john wayne writes about everything."</code>
<code> </code><code>;</code>
<code>string patternstring1 =</code><code>"(john) (.+?) "</code><code>;</code>
<code> </code><code>system.out.println(</code><code>"found: "</code> <code>+ matcher.group(</code><code>1</code><code>) +</code>
<code> </code><code>" "</code> <code>+ matcher.group(</code><code>2</code><code>));</code>
在正则表达式中分组可以嵌套分组,例如
<code>((john) (.+?))</code>
这是之前的例子,现在放在一个大分组里.(表达式末尾有一个空格)。
当遇到嵌套分组时, 分组编号是由左括号的顺序确定的。上例中,分组1 是那个大分组。分组2 是包括john的分组,分组3 是包括 .+? 的分组。当需要通过groups(int groupno) 引用分组时,了解这些非常重要。
以下代码演示如何使用嵌套分组
<code>string patternstring1 =</code><code>"((john) (.+?)) "</code><code>;</code>
<code> </code><code>system.out.println(</code><code>"found: "</code><code>);</code>
replaceall() 和 replacefirst() 方法可以用于替换matcher搜索字符串中的一部分。replaceall() 方法替换全部匹配的正则表达式,replacefirst() 只替换第一个匹配的。
在处理之前,matcher 会先重置。所以这里的匹配表达式从文本开头开始计算。
示例如下
<code>string replaceall = matcher.replaceall(</code><code>"joe blocks "</code><code>);</code>
<code>system.out.println(</code><code>"replaceall = "</code> <code>+ replaceall);</code>
<code>12</code>
<code>string replacefirst = matcher.replacefirst(</code><code>"joe blocks "</code><code>);</code>
<code>13</code>
<code>system.out.println(</code><code>"replacefirst = "</code> <code>+ replacefirst);</code>
输出如下
<code>replaceall = joe blocks about this, and joe blocks writes about that, and joe blocks writes about everything. replacefirst = joe blocks about this, and john doe writes about that, and john wayne writes about everything.</code>
输出中的换行和缩进是为了可读而增加的。
注意第1个字符串中所有出现 john 后跟一个单词 的地方,都被替换为 joe blocks 。第2个字符串中,只有第一个出现的被替换。
appendreplacement() 和 appendtail() 方法用于替换输入文本中的字符串短语,同时把替换后的字符串附加到一个 stringbuffer 中。
当find() 方法找到一个匹配项时,可以调用 appendreplacement() 方法,这会导致输入字符串被增加到stringbuffer 中,而且匹配文本被替换。 从上一个匹配文本结尾处开始,直到本次匹配文本会被拷贝。
appendreplacement() 会记录拷贝stringbuffer 中的内容,可以持续调用find(),直到没有匹配项。
直到最后一个匹配项目,输入文本中剩余一部分没有拷贝到 stringbuffer. 这部分文本是从最后一个匹配项结尾,到文本末尾部分。通过调用 appendtail() 方法,可以把这部分内容拷贝到 stringbuffer 中.
查看源代码
打印帮助
<code>pattern pattern = pattern.compile(patternstring1);</code>
<code>matcher matcher = pattern.matcher(text);</code>
<code>stringbuffer stringbuffer =</code><code>new</code> <code>stringbuffer();</code>
<code>while</code><code>(matcher.find()){</code>
<code> </code><code>matcher.appendreplacement(stringbuffer,</code><code>"joe blocks "</code><code>);</code>
<code> </code><code>system.out.println(stringbuffer.tostring());</code>
<code>14</code>
<code>15</code>
<code>matcher.appendtail(stringbuffer);</code>
<code>16</code>
<code>system.out.println(stringbuffer.tostring());</code>
为了更有效的使用正则表达式,需要了解正则表达式语法。正则表达式语法很复杂,可以写出非常高级的表达式。只有通过大量的练习才能掌握这些语法规则。
本篇文字,我们将通过例子了解正则表达式语法的基础部分。介绍重点将会放在为了使用正则表达式所需要了解的核心概念,不会涉及过多的细节。详细解释,参见 java doc 中的 pattern 类.
在介绍高级功能前,我们先快速浏览下正则表达式的基本语法。
是正则表达式中最经常使用的的一个表达式,作用是简单的匹配一个确定的字符。例如:
<code>john</code>
这个简单的表达式将会在一个输入文本中匹配john文本。
可以在表达式中使用任意英文字符。也可以使用字符对于的8进制,16进制或unicode编码表示。例如:
<code>101 \x41 \u0041</code>
以上3个表达式 都表示大写字符a。第一个是8进制编码(101),第2个是16进制编码(41),第3个是unicode编码(0041).
字符分类是一种结构,可以针对多个字符匹配而不只是一个字符。换句话说,一个字符分类匹配输入文本中的一个字符,对应字符分类中多个允许字符。例如,你想匹配字符 a,b 或c,表达式如下:
<code>[abc]</code>
用一对方括号[] 表示字符分类。方括号本身并不是要匹配的一部分。
可以用字符分类完成很多事。例如想要匹配单词john,首字母可以为大写和小写j.
<code>[jj]ohn</code>
字符分类[jj] 匹配j或j,剩余的 ohn 会准确匹配字符ohn.
正则表达式中有一些预定义的字符分类可以使用。例如, \d 表示任意数字, \s 表示任意空白字符,\w 表示任意单词字符。
预定义字符分类不需要括在方括号里,当然也可以组合使用
<code>\d [\d\s]</code>
第1个匹配任意数字,第2个匹配任意数字或空白符。
完整的预定义字符分类列表,在本文最后列出。
正则表达式支持匹配边界,例如单词边界,文本的开头或末尾。例如,\w 匹配一个单词,^匹配行首,$ 匹配行尾。
<code>^this is a single line$</code>
上面的表达式匹配一行文本,只有文本 this is a single line。注意其中的行首和行尾标志,表示不能有任何文本在文本的前面后后面,只能是行首和行尾。
完整的匹配边界列表,在本文最后列出。
量词可以匹配一个表达式多次出现。例如下列表达式匹配字母a 出现0次或多次。
<code>a*</code>
量词 * 表示0次或多次。+ 表示1次或多次。? 表示0次或1次。还有些其他量词,参见本文后面的列表。
量词匹配分为 饥饿模式,贪婪模式,独占模式。饥饿模式 匹配尽可能少的文本。贪婪模式匹配尽可能多的文本。独占模式匹配尽可能多的文本,甚至导致剩余表达式匹配失败。
以下演示饥饿模式,贪婪模式,独占模式区别。假设以下文本:
<code>john went for a walk, and john fell down, and john hurt his knee.</code>
饥饿模式下 表达式:
<code>john.*?</code>
这个表达式匹配john 后跟0个或多个字符。 . 表示任意字符。* 表示0或多次。? 跟在 * 后面,表示 * 采用饥饿模式。
饥饿模式下,量词只会匹配尽可能少的字符,即0个字符。上例中的表达式将会匹配单词john,在输入文本中出现3次。
如果改为贪婪模式,表达式如下:
<code>john.*</code>
贪婪模式下,量词会匹配尽可能多的字符。现在表达式会匹配第一个出现的john,以及在贪婪模式下 匹配剩余的所有字符。这样,只有一个匹配项。
最后,我们改为独占模式:
<code>john.*+hurt</code>
*后跟+ 表示独占模式量词。
这个表达式在输入文本中没有匹配项,尽管文本中包括 john 和 hurt. 为什么会这样? 因为 .*+ 是独占模式。与贪婪模式下,尽可能多的匹配文本,以使整个表达式匹配不同。独占模式会尽可能的多的匹配,但不考虑表达式剩余部分是否能匹配上。
.*+ 将会匹配第一个john之后的所有字符,这会导致表达式中剩余的 hurt 没有匹配项。如果改为贪婪模式,会有一个匹配项。表达式如下:
<code>john.*hurt</code>
正则表达式支持少量的逻辑运算(与,或,非)。
与操作是默认的,表达式 john ,意味着j 与 o与h与n。
或操作需要显示指定,用 | 表示。例如表达式 john|hurt 意味着john 或 hurt 。
.
任意英文字母
\\
反斜杠, 单独的反斜杠做为转义字符,与其他特殊字符一起使用。如果想匹配反斜杠本身,需要转义。两个反斜杠实际匹配一个反斜杠n字符的8进制表示.n 在0至7之间取值
nn
字符的8进制表示.n 在0至7之间取值
mnn
字符的8进制表示. m 在0至3之间取值, n 在0至7之间取值
\xhh
字符的16进制表示.
\uhhhh
字符的16进制表示 0xhhhh. 对应unicode 编码字符
\t
缩进符.
\n
换行符 (unicode: ‘\u000a’)
\r
回车符 (unicode: ‘\u000d’)
\f
制表符 (unicode: ‘\u000c’)
\a
警报(铃声)字符 (unicode: ‘\u0007′)
\e
转义符 (unicode: ‘\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-z&&[def]]
匹配 d, e, 或 f. 是交集匹配 (这里是在范围 a-z和字符def之间取交集).
[a-z&&[^bc]]
匹配a-z 之间所有字符,排除bc的字符。是减法匹配
[a-z&&[^m-p]]
匹配a-z 之间所有字符,排除m-p之间的字符是减法匹配
匹配任意一个字符,根据创建pattern是传入的标志,可能匹配行结尾符
\d
匹配任意数字 [0-9]
匹配任意非数字 [^0-9]
\s
匹配任意空白符 (空格, 缩进, 换行,回车)
匹配任意非空白符
\w
匹配任意单词
匹配任意非单词
^
匹配行首
$
匹配行尾
\b
匹配单词边界
匹配非单词边界
匹配文本开头
\g
匹配前一匹配项结尾
\z
matches the end of the input text except the final terminator if any
匹配文本结尾
贪婪模式
饥饿模式
独占模式
x?
x??
x?+
匹配0或1次
x*
x*?
x*+
匹配0或多次
x+
x+?
x++
匹配1或多次
x{n}
x{n}?
x{n}+
匹配n次
x{n,}
x{n,}?
x{n,}+
匹配最少n次
x{n, m}
x{n, m}?
x{n, m}+
匹配最少n次,最多m次