Java正则表达式

正则表达式基础

参考书籍:精通正则表达式。

Java正则表达式API

java.util.regex程序包只包含用于实现Java正则表达式处理技术的两个类,分别名为PatternMatcher。自然而然你会想到正则表达式由模式匹配(pattern matching)而成。java.lang还定义了一个新接口,它支持这些新的类。在研究Patternt和Matcher之前,我们先快速浏览一下CharSequence这一新概念。另外,为方便起见String类为运行正则表达式匹配提供了一些新程序作为捷径

CharSequence接口

正则表达式是根据字符序列进行模式匹配的。虽然String对象封装了字符序列,但是它们并不是能够这样做的唯一对象。

JDK 1.4定义了一个名为CharSequence的新接口,可描述特定不变的字符序列。该新接口是一个抽象(abstraction),它把字符序列从包含这些字符的具体实现(specific implementation)中分离出来。JDK 1.4对“年高德勋”的String和StringBuffer类进行了改进,用于实现CharSequence接口。新的CharBuffer类也实现了CharSequence。CharSequence接口也在字符集映射中投入了使用

CharSequence定义的API十分简单。毕竟它没有花太多“笔墨”描述字符序列。

package java.lang;
public interface CharSequence
{
    int length();
    char charAt (int index);
    public String toString();
    CharSequence subSequence (int start, int end);
}

CharSequence描述的每个字符序列通过length( )方法会返回某个长度值。通过调用charAt( )可以得到序列的各个字符,其中索引是期望的字符位置(desired character position)。字符位置从零到字符序列的长度之间,与我们熟悉的String.charAt( )基本一样。

toString( )方法返回的String对象包括所描述的字符序列。这可能很有用,如打印字符序列。正如之前提过的,String现在实现了CharSequence。String和CharSequence同为不变的,因此如果CharSequence描述一个完整的String,那么CharSequence的toString( )方法返回的是潜在的String对象而不是副本。如果备份对象是StringBuffer或CharBuffer,系统将创建一个新的String保存字符序列的副本。

最后通过调用subSequence( )方法会创建一个新的CharSequence描述子范围(subrange)。start和end的指定方式与String.substring( )的方式相同:start必须是序列的有效索引(valid index);end必须比start大,标志的是最末字符的索引加一。换句话说,start是起始索引(计算在内),end是结束索引(不计算在内)。

CharSequence接口因为没有赋值方法(mutator method)看上去似乎是不变的,但是基本的实现对象可能不是不变的。CharSequence方法反映了基本对象的现状。如果状态改变,CharSequence方法返回的信息同样会发生变化。如何你依赖CharSequence保持稳定且不确认基础的实现,你可以调用toString( )方法,对字符序列拍个真实不变的快照。

Pattern类

Pattern类封装了正则表达式,它是你希望在目标字符序列中检索的模式。匹配正则表达式的代价可能非常高昂,因为可能排列数量巨大,尤其是模式反复应用的情况。大部分正则表达式处理器(包括Perl在内,在封装中)首先会编译表达式,然后利用编译好的表达式在输入中进行模式检测。

在这一点上Java正则表达式程序包别无两样。Pattern类的实例是将一个编译好的正则表达式封装起来。让我们看看完整的Pattern API,看看它是如何使用的。记住,这并不是一个句法完整的类文件,它只中去掉了类主体的方法签名。

package java.util.regex;
public final class Pattern implements java.io.Serializable
{
    public static final int UNIX_LINES
    public static final int CASE_INSENSITIVE
    public static final int COMMENTS
    public static final int MULTILINE
    public static final int DOTALL
    public static final int UNICODE_CASE
    public static final int CANON_EQ
    public static boolean matches (String regex, CharSequence input)
    public static Pattern compile (String regex)
    public static Pattern compile (String regex, int flags)
    public String pattern()
    public int flags()
    public String[] split (CharSequence input, int limit)
    public String[] split (CharSequence input)
    public Matcher matcher (CharSequence input)
}

上面所列的第一个方法matches( )是个公用程序。它可以进行完整的匹配操作,并根据正则表达式是否匹配整个的(entire)输入序列返回一个布尔值。这种方法很容易上手,因为你无须追踪任何对象;你要做的仅是调用一个简单的静态方法并测试结果。

public boolean goodAnswer (String answer)
{
return (Pattern.matches ("[Yy]es|[Yy]|[Tt]rue", answer));
}

这种方法适用于默认设置尚可接受并且只需进行一次测试的情况。假如你要重复检查同一模式,假如你要找的模式是输入的子序列,又假如你要设置非默认选项,那么你应当创建一个新的Pattern对象并使用新对象的API方法。 

需要注意的是Pattern类并没有public 构造函数。只有通过调用静态工厂方法才可以创建新的实例。compile( )的两个形式采用的都是正则表达式的String参数。返回的Pattern对象包含被转换成已编译内部形式的正则表达式。如果你提供的正则表达式形态异常,那么compile( )工厂方法会抛出java.util.regex.PatternSyntaxException(模式句法异常)。这是未经检查的异常,因此如果你对自己使用的正则表达式是否可行存在疑虑(例如它传递给你是一个变量),那么你可以把对compile( )的调用放到try/catch块中进行检测。

compile( )的第二种形式接受标志有一个位掩码,这影响了正则表达式的默认编译。这些标志启用了可选的编译模式行为,例如如何处理边界或不区分大小写等。(除CANOB_EQ外)这些标志(flag)同样可由嵌入表达式内的子表达式启用。标志可以与布尔或(OR)表达式结合使用,如下所示:

Pattern pattern = Pattern.compile ("[A-Z][a-zA-Z]*",
Pattern.CASE_INSENSITIVE | Pattern.UNIX_LINES);

Pattern类的实例是不变的,各个实例与对应的正则表达式绑定,无法修改。Pattern对象也是线程安全的,可被多个线程同时使用。

Matcher类

Matcher类为匹配字符序列的正则表达式模式提供了丰富的API。Matcher实例常常通过对Pattern对象调用matcher( )方法来创建的,它常常采用由该Pattern封装的正则表达式:

Matcher类的实例是监控状态型对象,它们封装了与特定输入字符序列匹配的具体正则表达式。Matcher对象并不是线程安全的,因为它们在方法调用之间有保有内状态(hold internal state)。一个Matcher实例来自一个Pattern实例,Matcher对象的pattern( )返回的是向后引用(back reference),指向创建了Matcher的Pattern对象。Matcher对象可以重复使用,但是因其监控状态属性,为了开始新匹配操作它们必须处于已知状态。这可通过调用reset( )方法来实现,该方法在与匹配程序有关的CharSequence之前为模式匹配备好了对象。无参数的reset( )将使用上次为Matcher设置的CharSequence。如果你希望对新的字符序列进行匹配,那么你可以将一个新的CharSequence传递给reset( ),随后匹配将针对目标进行。例如,随着你读取各行的文件,你可以把它传递给reset( )。

matches( ),如果整个(entire)字符序列匹配正则表达式的模式,则它返回true。反之如果模式匹配的只是子序列,方法将返回false。在文件中,这种方法用于选取恰好满足一定模式的行是非常有用的。这种行为(behavior)与作用于Pattern类的公用程序matches( )相同。

lookingAt( )方法与matches( )相似,但是它不要求整个序列的模式匹配。如果正则表达式模式匹配字符序列的beginning(开头),则lookingAt( )返回true。lookingAt( )方法往往从序列的头部开始扫描。该方法的名字暗示了匹配程序正在“查看”目标是否以模式开头。如果返回为true,那么可以调用start( )、end( )和group( )方法匹配的子序列的范围(随后将给出更多关于这些程序的内容)。

find( )方法运行的是与lookingAt( )相同类型的匹配操作,但是它会记住前一个匹配的位置并在之后重新开始扫描。从而允许了相继调用find( )对输入进行逐句比对,寻找嵌入的匹配。复位后第一次调用该方法,则扫描将从输入序列的首个字符开始。在随后调用中,它将从前一个匹配的子序列后面的第一个字符重新开始扫描。如各个调用来说,如果找到了模式将返回true;反之将返回false。通常你会使用find( )循环访问一些文本来查找其中所有匹配的模式。

带位置参数的find( )会在给定的索引位置进行隐式复位并从该位置开始扫描。然后如果需要可以调用无参数的find( )扫描输入序列剩余的部分。

一旦检查到匹配,你可以通过调用start( )和end( )确定匹配位于字符序列的什么位置。Start( )方法返回的是匹配序列首个字符的索引;end( )方法返回的值等于匹配序列最末字符的索引加一。这些返回值与CharSequence.subsequence( )的返回值一致,可直接用于提取匹配的子序列。

 

CharSequence subseq; 
if (matcher.find( )) 
{ 
subseq = input.subSequence (matcher.start(), matcher.end( )); 
}

一些正则表达式可以匹配空字符串,这种情况下start( )和end( )将返回相同的值。只有当匹配之前已经过matches( )、lookingAt( )或检测find( )的检测,start( )和end( )返回的值才有意义。如果没有检测到匹配或最后的匹配尝试返回的是false,那么调用start( )或end( )将导致java.lang.IllegalStateException(Java语言非法状态异常)。为了了解带有group参数的start( )和end( ),我们首先需要知道表达式捕获组(expression capture group)。

Java正则表达式_第1张图片

正则表达式可能包含称为捕获组(capture group)的子表达式,它们被小括号括了起来。在正则表达式的求值期间将保存匹配这些捕获组表达式的输入子序列。一旦完全匹配操作完成,这些保存的代码片断可通过确定相应的组号从Matcher对象上重新获取。捕获组可以嵌套使用,数量可以通过从左到右计算左括弧(开括号)得到。无论整个表达式是否有子组,它的捕获组总能记为组零(group zero)。例如,正则表达式A((B)(C(D)))可能有的捕获组编号如表5-3所示。

Java正则表达式_第2张图片

 这种分组句法存在异常事件。以(?开头的组是个纯的(pure)或说是无法捕获的组。它的值无法保存且它对无法计算捕获组编号。

捕获组在正则表达式模式中的编号由groupCount( )方法返回。该值来自原始的Pattern对象,是不可变的。组号必须为正且小于groupCount( )返回的值。传递超出范围的组号将导致java.lang.IndexOutOfBoundsException(java语言索引出界异常)。

可以将捕获组号传递给start( )和end( )来确定子序列是否匹配已知的捕获组子表达式。有可能出现这样一种情况,即整个表达式成功匹配但是有一个或多个的捕获组无法匹配。如果请求的捕获组当前没有设置则start( )和end( )方法的返回值将为-1。

(正如之前看到的)你可以利用start( )和end( )返回的值从输入的CharSequence中提取出匹配的子序列,但是group( )方法为此提供了更简单的方式。调用带数字参数的group( )将返回一个字段,该字段是匹配特殊捕获组的子序列。如果你调用的group( )不含参数,则返回将是与整个正则表达式(组零)匹配的子序列。


String match0 = input.subSequence (matcher.start(), matcher.end()).toString( );
String match2 = input.subSequence (matcher.start (2), matcher.end (2)).toString( );
上述代码与下列代码等效:
String match0 = matcher.group( );
 String match2 = matcher.group(2);

最后让我们看看Matcher对象解决修改字符序列的方法。正则表达式最常见的应用之一是查找并替换(search-and-replace)。这种应用使用replaceFirst( )和replaceAll( )可以轻轻松松就搞定。它们的行为方式是相同的,区别在于replaceFirst( )在找到第一个匹配后就会停止,而replaceAll( )将循环执行直到替换完所有的匹配。二者都带有String参数,String参数是用于替换输入字符序列中匹配模式的替换值(replacement value) 

上文提过,捕获组在正则表达式内可以向后引用(back-reference)。它们也可以被你提供组replaceFirst( )或replaceAll( )的替换字符串引用。捕获组号通过添加美元符号$可嵌入替换字符串中。当替换字符串被替换成结果字符串时,每次出现的$g将被group( )返回的值代替。如果你想在替换字符串使用字面量(literal)美元符号,那么你必须在它前面加个反斜杠符号(\$)。如果想要传递反斜杠符号,你必须多加一个反斜杠(\\)。如果你想在捕获组引用后面跟上字面量的数值型数字,那么你可以用反斜杠将它们与组号分开,像这样:123$2\456。

Matcher对象记住的状态信息位(the bits of state information)之一是追加位置(append position)。追加位置是用于记住输入字符序列的量,这些字符序列已经通过之前调用appendReplacement( )复制了出来。当调用appendReplacement( )时,将发生如下过程:
1. 从输入中读取字符是从当前追加位置开始,读取的字符将被添加到已知的StringBuffer中。最后复制的字符就在匹配模式的首个字符之前。这个字符位于start( )返回的索引减一的位置。
2. 如先前描述的,替换字符串被添加给StringBuffer并替换任何嵌入的捕获组引用。
3. 追加位置更新成跟在匹配模式后面的字符的索引,这个索引是end( )返回的值。
仅当前一个匹配操作成功(通常调用find( ))appendReplacement( )方法才能正常工作。如果前一个匹配返回的是false或在复位后立即调用该方法,你将得到一个“令人愉快的奖励”:java.lang.IllegalStateException(java语言非法状态异常)。

String类的正则表达式方法

 
package java.lang;
public final class String
implements java.io.Serializable, Comparable, CharSequence
{
    // This is a partial API listing
    public boolean matches (String regex)
    public String [] split (String regex)
    public String [] split (String regex, int limit)
    public String replaceFirst (String regex, String replacement)
    public String replaceAll (String regex, String replacement)
}

正则表达式语句

参考:Java Pattern类的用法详解(正则表达式)

 

你可能感兴趣的:(#,Java基础知识,#,Netty)