目录

一、前言

正则表达式作为文本处理的利器,早已成为各大语言的必要装备,但各语言对其的实现程度(功能语法支持程度)和API设计均有所差异,本篇将目光投向java原生类库中提供的正则表达式API—— java.util.regex包 和 java.lang.String实例方法 ,和所支持的功能语法上。

二、 正则表达式的使用诉求

正则表达式一般用于处理如下诉求,本篇后续内容将以这些诉求为基础检验相关的原生API是否提供方便有效(code less,do more)的操作方式。

1. 匹配字符串:全字符串匹配、部分匹配(也就是包含关系)

2. 替换字符串

3. 萃取字符串

4. 拆分字符串

三、 java.util.regex包

从jdk1.5开始正则表达式相关API就集中存放在该包下,且为其他类中表达式相关方法提供基础处理能力。

1.  java.util.regex.Pattern类 :模式类,用于对正则表达式进行编译。

类方法:

/** 对正则表达式进行编译,并返回Pattern实例

* 入参flag作为表达式属性,启用多个表达式属性时,采用管道符(|)连接多个表达式属性。除了通过入参的方式设置表达式属性,还可以使用嵌入式标识来设置表达式属性,格式为:(?表达式属性1表达式属性2)正则表达式,示例——不区分大小写和全局匹配abcd:(?ig)abcd*/Pattern compile(String regex);

Pattern compile(String regex,intflag);//字符串完整匹配表达式的快捷方式,内部依然是//Pattern p = Pattern.compile(regex);//p.matcher(input).matches();

boolean matches(String regex, CharSequence input);//返回可以配置入参s的字面量模式。注意格式为\\Q表达式\\E。表达式中的元字符将当作普通字符处理

String quote(String s);

表达式属性:

//以\n作为换行符,内嵌为(?d)

Pattern.UNIX_LINES//US-ASCII编码字符不区分大小写,内嵌为(?i)

Pattern.CASE_INSENSITIVE//忽略空格和注释(注释为以#开头直到出现换行符),内嵌为(?x)

Pattern.COMMENTS//启动多行模式,^和$匹配换行符或字符串起始位置。默认为单行模式,^和$仅匹配字符串起始位置。内嵌为(?m)

Pattern.MULTILINE//字面量模式,将元字符当作普通字符处理,没有内嵌方式,但可以通过"\\Q正则表达式\\E"的方式实现

Pattern.LITERAL//元字符.将匹配换行符。默认情况下,元字符.不匹配换行符。内嵌为(?s)

Pattern.DOTALL//UNICODE编码字符不区分大小写,内嵌为(?u)

Pattern.UNICODE_CASE//当且仅当正则分解匹配时才配置成功。

Pattern.CANON_EQ//启用Unicode版本的预定义字符类和POSIX字符类,内嵌为(?U)

Pattern.UNICODE_CHARACTER_CLASS

实例方法:

//返回正则表达式

String pattern();//使用正则表达式匹配的字符串切割入参input//入参limit用于设置返回数组长度的最大值,设置为0时则不限制最大值。

String[] split(CharSequence input);

String[] split(CharSequence input,intlimit);//获取匹配类

Matcher matcher(CharSequence input);

2. java.util.regex.Matcher类 :匹配类,用于存储模式实例匹配某字符串后所产生的结果。

静态方法:

//将入参s中的\和$元字符转换为普通字符,并返回处理后的s字符串。

String quoteReplacement(String s)

实例方法:

//获取匹配子字符串的起始索引

intstart();//获取匹配子字符串的结束索引

intend();//从字符串的end+1位置开始搜索下一个匹配的字符串

boolean find();

boolean find(intstart);//通过分组索引获取分组内容,若入参group超出分组数量则抛异常

String group();

String group(intgroup);//通过分组名称获取分组内容,若没有相应的分组则返回null

String group(String name);//重置匹配实例内部的状态属性

Matacher reset();//重置匹配实例内部的状态属性,并重置被匹配的字符串

Matacher reset(CharSequence input);//重置模式实例,这导致group信息丢失,但注意:start等信息依旧保留不变。

Matcher usePattern(Pattern newPattern);//从字符串起始位开始将匹配成功的子字符串均用入参replacement替换掉

String replaceAll(String replacement);//从字符串起始位开始将第一个匹配成功的子字符串均用入参replacement替换掉

String replaceFirst(String replacement);//将从字符串起始位开始到最后一匹配的子字符串最后一个字符的位置的字符串复制到sb中,并用入参replacement替换sb中匹配的内容

String appendReplace(StringBuffer sb, String replacement);//将剩余的子字符串复制到sb中

String appendTail(StringBuffer sb);//示例: sb为one dog two dog

Matcher m = p.matcher("one cat two cats in the yard");

StringBuffer sb= newStringBuffer();while(m.find()) {

m.appendReplacement(sb,"dog");

}//字符串从头到尾匹配表达式

boolean matches();//从字符串起始位置开始匹配表达式,但不要字符串从头到尾匹配表达式

boolean lookingAt();

四、 java.lang.String实例

实例方法:

/**
* 若要将\替换为\\,则需要写成 replaceAll("\\\\", "\\\\\\\\")
* 由于参数regex和replacement都被正则表达式引擎识别使用,因此书写 "\\\\" 首先会被Java编译器识别为字符串 "\\",然后被正则表达式引擎识别为 "\"。
*/
String replaceAll(String regex, String replacement);
String replaceFirst(String regex, String replacement);
/**
* 将字符串中的某一字符全部替换为指定的字符。
*/
String replace(char oldChar, char newChar);
String[] split(String regex);
String[] split(String regex,intlimit);
boolean matches(String regex)

五、最短路径实现诉求

final classRegExp{//全字符串匹配
public staticboolean isMatch(String regex, String input){if (null == input) return false;returninput.matches(regex);
}//包含子字符串
public staticboolean contains(String regex, String input){
Pattern r=Pattern.compile(regex);returnr.matcher(input).find();
}//实现indexOf
public static intindexOf(String regex, String input){
Pattern r=Pattern.compile(regex);
Matcher m=r.matcher(input);int index = -1;if(m.find())
index=m.start();returnindex;
}//实现lastIndexOf
public static intlastIndexOf(String regex, String input){
Pattern r=Pattern.compile(regex);
Matcher m=r.matcher(input);int index = -1;while(m.find())
index=m.start();returnindex;
}//替换全部匹配字符串
public staticString replaceAll(String regex, String input, String replacement){if (null == regex || regex.isEmpty()) returninput;returninput.replaceAll(regex, replacement);
}//替换第N个匹配字符串
public static String replaceSome(String regex, String input, String replacement, intn){if (null == regex || regex.isEmpty()) returninput;if (0 == n) returninput.replaceFirst(regex, replacement);
Pattern r=Pattern.compile(regex);
Matcher m=r.matcher(input);int i = 0;
StringBuffer buffer= newStringBuffer();while (i <= n &&m.find()){if (i ==n){
m.appendReplacement(buffer, replacement);
m.appendTail(buffer);
}++i;
}if (0 ==buffer.length())
buffer.append(input);returnbuffer.toString();
}//萃取字符串
public staticString extract(String regex, String input){
String ret= "";
Pattern r=Pattern.compile(regex);
Matcher m=r.matcher(input);if(m.find())
ret=m.group();returnret;
}//拆分字符串
public static String[] split(String regex, String input, intlimit){if (null == input ||input.isEmpty()|| null == regex || regex.isEmpty()) return newString[]{input};returninput.split(regex, limit);
}
}

实际应用时当然不会像上面那么简单了。

六、Java支持的正则表达式功能语法

本节内容仅针对正则表达式的高级功能语法进行叙述,而各语言的正则实现也就是这部分有所差异而已。

1. 分组及反向引用

[a].  (子表达式),自动命名分组(从1开始以数字自动为分组命名),后续表达式中可通过反向引用来获取该分组的内容。例如匹配字符串“so so”的正则表达式可以是 ^(\w{2})\s(\1)$ ,其中 \1 就是反向引用。

[b]. (?:子表达式),非捕获分组,该类型的分组将不纳入匹配对象的group属性中,并且无法通过反向引用在表达式的后续部分获取该分组的内容。通常是配合 | 使用。例如匹配字符串"so easy"和"so hard"的正则表达式可以是 so\s(?:easy|hard)

[c].  (?子表达式),命名分组,该类型的分组将纳入匹配对象的group属性中,并且可以在group属性值中通过name值来获取该分组的值。

[d].  (?#注释),注释分组,该类型分组的内容将被正则表达式编译器忽略,仅供码农查阅而已。

2. 零宽先行断言

零宽先行断言初看之下有点不知所云的感觉, 那么我们拆开来分析一下它的意思吧!

零宽——意思是匹配的子表达式将不被纳入匹配结果,仅作为匹配条件而已。

先行——意思是子表达式匹配的是后续字符串的内容。

并且其细分为两类:

[a].  子表达式B(?=子表达式A),零宽正向先行断言(也称为预搜索匹配)。例如匹配字符串"abcd"中的a和b的正则表达式可以是 \w(?=\w{2})

[b].  子表达式B(?!子表达式A),零宽负向先行断言(也称为预搜索不匹配)。例如匹配字符串"abcd"中的c和d的正则表达式可以是 \w(?!\w{2})

3. 零宽后行断言

后行——意思是子表达式匹配的是前面字符串的内容。

[a]. (?<=子表达式A)子表达式B,零宽正向后行断言(也称为反向搜索匹配)。例如匹配字符串"abcd"中的c和d的正则表达式可以是 (?<=\w{2})\w

[b].(?

4. 平衡组

作用:用于匹配左右两边开始、结束符号数量对等的字符串。

示例——萃取"


child

"的子字符串"