1 匹配
正则表达式的匹配功能就是判断指定字符串是否满足正则表达式的规则,涉及到String类的matches方法。这一方法的特点是,使用正则表达式匹配整个字符串,只要有一个字符位上的字符不匹配,则匹配失败,该字符位以后的字符则不再继续检测。
注:代码号码接上篇。
字符类符号匹配:
代码4:
class RegexDemo2
{
publicstatic void main(String[] args)
{
String[] strs = {"J24", "r87", "ds9z8", "c7e"};
/*
检测第一位字符是否包含在“a-g”和“w-z”范围内,
第二位字符是否包含在“1-3”和“7-9”范围内,
第三位字符是否包含在“a-g”和“1-6”范围内。
*/
String regex = "[a-z&&[^g-w]][1-3[7-9]][a-g1-6]";
System.out.println("规则:"+regex);
boolean result;
for(int x=0; x<strs.length; x++){
result = strs[x].matches(regex);
System.out.println(strs[x]+":"+result);
}
}
}
以上代码的执行结果为:
规则:[a-z&&[^g-w]][1-3[7-9]][a-g1-6]
J24:false
r87:false
ds9z8:false
c7e:true
预定义字符类匹配:
代码5:
class RegexDemo3
{
public static void main(String[] args)
{
String[] strs = {"g6\tj", "2&\rN", "0*a7", "8H1"};
/*
第一位为数字,第二位不能为数字
第三位为空白字符,第四位为单词字符
*/
String regex = "\\d\\D\\s\\w";
System.out.println("规则:"+regex);
boolean result;
for(int x=0; x<strs.length; x++){
result = strs[x].matches(regex);
System.out.println(strs[x]+":"+result);
}
}
}
执行以上代码的结果为:
规则:\d\D\s\w
g6 j:false
N:true
0*a7:false
8H 1:true
数量词的应用:
代码6:
class RegexDemo4
{
public static void main(String[] args)
{
String[] str = {" ", "547a", "hf5","d6f"};
//至少有一个字母,后面可能有多个数字,也可能没有数字
String regex = "[a-zA-Z]+\\d*";
System.out.println("规则:"+regex);
boolean result;
for(int x=0; x<str.length; x++){
result= str[x].matches(regex);
System.out.println(result);
}
}
}
执行结果为:
规则:[a-zA-Z]+\d*
:false
547a:false
hf5:true
d6f:false
由于空格不算字母,因此“ ”匹配失败;表达式规定先字母后数字,因此“547a”匹配失败;“hf5”匹配成功;数字后不能再有字母,因此“d6f”匹配失败。其他数量词的应用,请大家自行尝试。
根据以上演示示例我们就可以分析代码3中所使用的正则表达式的含义:[1-9],表示第一个字符位必须为1-9的数字;[0-9],表示第二字符位必须是为0-9的数字;{4,14},表示符合第二个规则的字符,将至少重复出现4,至多出现14次,换句话说,第2至第15位上的数字都必须在0-9范围内。
匹配练习:检测指定字符串是否为手机号。手机号均以“13”、“15”或者“18”开头,并且号码长度必须为11个。
代码7:
class RegexDemo5
{
public static void main(String[] args)
{
String[] numbers = {"15571803416", "13901265397", "20894584093", "1809361", "16478064263","1302g839316"};
/*
第一位必须是1,第二位必须是3、5、8中的一个数字
第3至第11位只要是数字就行
*/
String regex= "1[358]\\d{9}";
System.out.println("规则:"+regex);
booleanresult;
for(int x=0; x<numbers.length; x++){
result = numbers[x].matches(regex);
System.out.println(numbers[x]+":"+result);
}
}
}
执行结果为:
规则:1[358]\d{9}
15571803416:true
13901265397:true
20894584093:false
1809361:false
16478064263:false
1302g839316:false
2 切割
正则表达式的第二个功能就是切割。所涉及到的方法,就是常用的split()方法,我们再来回顾一下这个方法:
publicString[] split(String regex):根据给定正则表达式的匹配拆分此字符窜。String类参数名regex,代表正则表达式。
在前面的内容中,我们通过split方法实现了对字符串的简单分割操作,比如使用“,”等标点符号,或者使用单个字符,对指定字符串进行分割操作。那么我们通过正则表达式可以按照更为复杂的规则,对字符串进行分割操作。下面我们通过演示若干示例,来介绍正则表达式的切割功能。
例1:将字符串通过数量不等的空格进行分割
代码8:
public static void splitDemo()
{
//各个单词之间使用数量不等的空格进行分割
String str = "Java C++ Python Perl";
//空白字符出现一次或多次
String regex = "\\s+";
String[] temp = str.split(regex);
for(String single : temp){
System.out.println(single);
}
}
执行结果为:
Java
C++
Python
Perl
例2:将字符串通过“.”进行分割。
需要注意的是,由于“.”属于预定义字符,代表任意字符。因此如果使用“任意”字符进行分割,那么结果必然是错误的。因此在定义正则表达式时,应该对“.”进行转义——“\.”。但是在字符串中并不存在“\.”这样的转义字符,因此将无法通过编译。此时就需要再加一个“\”将后一个“\” 转义为普通反斜线,这样普通反斜线“\”+“.”,即表示正则表达式中的普通“.”字符,最终的正则表达式为“\\.”。
代码9:
public static void splitDemo2()
{
//各个单词之间使用数量不等的空格进行分割
String str = "Java.C++.Python.Perl";
//使用普通字符“.”进行分割
String regex = "\.";
String[] temp = str.split(regex);
for(String single : temp){
System.out.println(single);
}
}
执行结果为:
Java
C++
Python
Perl
例3:将对文件路径进行分割,使得盘符、各文件夹名称,以及文件名成为单独的字符串。由于字符串形式的文件路径中,文件之间都是通过双反斜线“\\”进行分隔。但是如果将正则表达式定义为“\\”,则仅代表一个反斜线符号。虽然双反斜线被解析为路径时(比如封装在File对象中时)被作为了单个路径分隔符,但是我们将其作为字符串进行操作时,字符串中的“\”字符确实是两个,因此正则表达式应定义为“\\\\”,其中每一对反斜线,代表字符串路径中的单个反斜线。
代码10:
public static void splitDemo3()
{
//对文件路径进行分割
String str = "C:\\File\\temp\\abc.txt";
//使用普通字符“.”进行分割
String regex = "\\\\";
String[] temp = str.split(regex);
for(String single : temp){
System.out.println(single);
}
}
执行结果为:
C:
File
temp
abc.txt
例4:将字符串通过重复字符进行分割。比如“JavaggC++TTTPythonqqPerl”,分割后应分别是“Java”、“C++”、“Pyhon”以及“Perl”。这里就需要用到组。由于这里需要匹配重复的字母,因此第一位规则为“([a-zA-Z])”,将其用括号括起来表示为一组。第二位以后的字符与第一位相同,就需要应用第一位的匹配结果,那么第二位规则为\\1。若要匹配多位重复字母,则再加一位“+”号,最终的正则表达式为“([a-Za-z]\\1+)”。
代码11:
public static void splitDemo4()
{
//对文件路径进行分割
String str = "JavaggC++TTTPythonqqPerl";
//使用普通字符“.”进行分割
String regex = "([a-zA-Z])\\1+";
String[] temp = str.split(regex);
for(String single : temp){
System.out.println(single);
}
}
执行结果为:
Java
C++
Python
Perl
3 替换
替换功能所涉及到的String类方法为replaceAll。
public String replaceAll(String regex,String replacement):使用给定的relacement替换此字符串所有匹配给定的正则表达式的子字符串。
例1:将字符串中5位以上的数字替换成“#”。
代码12:
public static void replaceAllDemo(){
String str = "abc5627424defg90387553bnoida21578134bjfpd18368975";
//匹配字符串中5位以上的数字
String regex = "\\d{5,}";
str = str.replaceAll(regex, "#");
System.out.println(str);
}
执行结果为:
abc#defg#bnoida#bjfpd#
例2:将字符串中的重复字母替换成“&”。
代码13:
public static void replaceAllDemo2(){
String str = "JavaggC++TTTPythonqqPerl";
String regex = "([a-zA-Z])\\1+";
str = str.replaceAll(regex, "&");
System.out.println(str);
}
执行结果为:
Java&C++&Python&Perl
例3:将字符串中的重复字符母换成单个字母。比如将“QQ”,替换成“Q”。这里同样涉及到对“组”的使用,所不同的是,替换字符串是正则表达式中的组所匹配的字符,因此需要对这一字符进行引用。在替换字符串中引用组使用“$”符号,比如“$1”,表示对第一组的引用。
代码14:
public static void replaceAllDemo3(){
String str = "6456GGG5736KK0183PPP";
String regex = "([a-zA-Z])\\1+";
str = str.replaceAll(regex, "$1");//引用第一组匹配字符
System.out.println(str);
}
执行结果为:
6456G5736K0183P
小知识点2:
假设“$”符号后面不接任何数字,默认表示对第零组的引用,也就是对整个字符串的引用,这一点需要大家注意。
4 获取
获取的意思就是,将原字符串中符合正则表达式规则的子串全部取出来。在前面的内容中,我们曾经介绍过,通过substring获取子串,但该方法只能通过角标进行获取,因此具有一定的局限性。
若要实现正则表达式的获取功能,不再是通过字符串的方法实现,而需要使用其他类,它的步骤如下:
第一步,将正则表达式封装成对象;
第二步,将正则表达式对象与被操作字符串相关量;
第三步,将两者关联后,获取正则匹配引擎;
第四步,通过引擎对符合规则的子串进行操作,比如获取操作。
4.1 Pattern类介绍
那么如何将正则表达式封装为对象呢?这就需要通过创建Pattern类实例对象将其封装起来。该类所属包为Java标准类库,java.util.regex包中,实际上前述正则表达式的API文档即包含在Pattern类的API文档描述中。
API文档:
正则表达式的编译表示形式。
方法:
该类并未对外提供构造函数,因此通常来说会定义静态方法返回本类对象。
public static Pattern compile(String regex):将给定的正则表达式编译到模式中。也就是将指定的字符串形式的正则表达式,封装为Pattern对象,并返回。
pubic Matcher matcher(CharSequence input):创建匹配给定输入与此模式的匹配器。该方法的参数类型为CharSequence,它是一个接口,String就是它的实现类之一,因此该方法可以接受被操作字符串。注意到该方法的返回值类型为Matcher。下面的内容中对Matcher类进行了介绍。
4.2 Matcher类介绍
API文档:
通过解释Pattern对character sequence执行匹配操作的引擎。Matcher类的实例对象就是根据指定模式对象生成的匹配器,或者成为匹配引擎。
通过调用模式(Pattern对象)的matcher方法从模式创建匹配器。创建匹配器后,可以使用它执行三种不同的匹配操作:
matches方法尝试将整个输入序列与该模式匹配;
lookingAt尝试将输入序列从头开始于该模式匹配。
find方法扫描输入序列以查找与该模式匹配的下一个子序列。
方法:
该类同样没有提供构造方法。
注意到该类提供了若干与String类相同的方法,比如replaceAll、matches方法等,实际上String类中的这些方法就是通过封装在其内部的Pattern和Matcher对象的方法实现的。
public boolean matches():尝试将整个区域与模式匹配。与String类方法相同。
public boolean find():尝试查找与该模式匹配的输入序列的下一个子序列。该方法的就是将正则表达式作用到字符串上,并进行匹配操作,若查找到匹配的字符串子串,则返回true。
public String group():返回由以前匹配操作所匹配的输入子序列。需要注意的是,在调用该方法以前,首先要通过find方法进行匹配以后,才能使用该方法获取获取匹配的子串。相反,在未进行任何匹配动作以前,若调用group方法将抛出IllegalStateException异常,并提示“No match found”。
public int end():返回最后匹配字符之后的偏移量。该方法返回的是匹配单词的末尾字母在字符串中的角标值。
public int start():返回以前匹配的初始索引。该方法返回的是匹配单词起始字母在字符串中的角标值。
4.2 正则表达式获取功能演示
例1:将某个字符串中包含3个及以上字母的单词全部取出。
思路:
从以上介绍可知,find方法的作用相当于判断,字符串中是否包含符合正则表达式规则的子串,而group方法相当于获取到这一子串。这一方法组合类似于集合类的迭代器,因此我们可以通过循环的方式将所有符合正则表达式规则的子串全部获取。
还需要注意的是,有些单词的长度超过了3个,因此仅通过上述方法,可能会获取到某个单词的一部分。因此需要正则表达式前后添加单词边界“\\b”,表示获取的自传前后必须包含空格,这样即可避免获取到某个单词的一部分。
代码15:
import java.util.regex.*;
class RegexDemo6
{
public static void main(String[] args)
{
//旅行给思想带来新的活力
String str = "Travel imparts new vigor to the mind.";
//字母连续出现3次子串,而且前后包含空格(单词结束标记)
String regex = "\\b[a-zA-Z]{3}\\b";
//将正则表达式封装为模式(Pattern)对象
Pattern pat = Pattern.compile(regex);
//将模式对象与被操作字符串关联起来
Matcher mat = pat.matcher(str);
/*
通过find方法向下判断字符串中是否还包含符合指定规则的子串
当判断存在时,则通过group方法返回这一子串
*/
while(mat.find()){
//获取匹配的单词起始和结束角标值,并打印
int start = mat.start(), end = mat.end();
System.out.println(mat.group()+":"+start+"--"+end);
}
}
}
执行以上代码的结果为:
new:15--18
the:28—31
代码说明:
如果在开始迭代匹配和获取子串之前,进行过一次或多次匹配操作,比如调用过find或者matches方法,那么无论匹配成功与否,Matcher对象内部的指针就会指向最后一次匹配的字符串角标位。由于同一个匹配器对于某个指定的字符串,使用同一个指针,此时再进行迭代获取子串,那么就会从上一次匹配的角标位开始匹配和获取,这样一来即使在这一角标位之前的字符串中有符合正则表达式规则的子串,也无法获取到了,需要大家注意这一点。