1 正则表达式简介
1.1 正则表达式的概念
正则表达式是一种强大而灵活的文本处理工具。使用正则表达式,我们能够以编程的方式,构造复杂的文本模式,并对输入的字符串进行搜索。一旦找到了匹配这些模式的部分,你就能够随心所欲地对它们进行处理了(摘自《Java编程思想》)。简单一句话,正则表达式就是专门用于操作字符串的,符合一定规则的表达式。
1.2 正则表达式的优缺点
我们在前面的《字符串》系列博客中,曾经详细介绍过有关操作字符串的各种方法,但是这些方法的功能都较为简单,而组合起来实现复杂功能时,相应的代码又过于复杂。正则表达式则可以使用较少的代码,同时实现较为复杂的字符串操作。
但是相应的越是复杂的字符串操作,对应的正则表达式就越复杂,阅读性也随之降低。这是正则表达式的弊端。
1.3 与传统方法的对比
我们先来看一个例子,通过这个例子来了解不使用正则表达式,而通过常规方法来进行复杂字符串操作的麻烦之处。
需求:对QQ号码进行校验。要求:能够对长度为5~15位的QQ号码进行校验,并且首位不能为0,号码中不能包含非数字字符。
思路:依次判断QQ号码长度、首位字符,以及循环判断号码中是否包含非数字字符。
代码1:
class RegexDemo
{
public static void main(String[] args)
{
String[] qqNumbers = {"9008", "1895828587923545452", "0123456", "458b16m54", "567890123"};
for(int i = 0; i<qqNumbers.length; i++){
isQQCount(qqNumbers[i]);
}
}
public static void isQQCount(String qqNumber)
{
int numLen = qqNumber.length();
//首先判断QQ号码长度
if(numLen >= 5 && numLen <= 15){
//其次判断QQ号码首位是否是0
if(!(qqNumber.charAt(0)== '0')){
char[]temp = qqNumber.toCharArray();
char c;
//循环判断QQ号码中是否包含非数字字符
for(int i = 0; i<numLen; i++){
c = temp[i];
if(c< '0' || c > '9'){
/*
一旦检测出包含有非数字字符
给出错误提示,并立即停止检测
*/
System.out.println("QQ号码中不能包含非数字字符!");
return;
}
}
//未检测处非法字符,则给出正确提示
System.out.println(qqNumber+":该QQ通过验证!");
}
else{
System.out.println("QQ号码不能以0开头!");
}
}
else
System.out.println("QQ号码长度错误!");
}
}
以上代码的执行结果为:
9008:QQ号码长度错误!
1895828587923545452:QQ号码长度错误!
0123456:QQ号码不能以0开头!
458b16m54:QQ号码中不能包含非数字字符!
567890123:该QQ通过验证!
以上代码是从字符串的角度,对QQ号码进行验证,我们还可以利用基本数据类型包装类的转换方法,让Java帮助我们进行判断。
思路2:号码长度和首位判断与思路1是一样的,而其他位号码的判断可以通过Long类的静态方法parseLong实现,如果尝试将包含非数字字符的字符串转换为长整型时,将抛出异常。
代码2:
public static void isQQCount2(String qqNumber)
{
int numLen = qqNumber.length();
if(numLen >= 5 && numLen <= 15){
if(!(qqNumber.charAt(0) == '0')){
try{
//将QQ号码字符串转换为长整型值
long qqNumberLongFormat = Long.parseLong(qqNumber);
//若转换成功,表示QQ号码验证成功
System.out.println(qqNumberLongFormat+":该QQ通过验证!");
}
catch(NumberFormatException e){
//若抛出异常,则表示QQ号码中包含了非数字字符,给出错误提示
System.out.println(qqNumber+":QQ号码中不能包含非数字字符!");
}
}
else
System.out.println(qqNumber+":QQ号码不能以0开头!");
}
else
System.out.println(qqNumber+":QQ号码长度错误!");
}
通过以上方法进行验证的结果为:
9008:QQ号码长度错误!
1895828587923545452:QQ号码长度错误!
0123456:QQ号码不能以0开头!
458b16m54:QQ号码中不能包含非数字字符!
567890123:该QQ通过验证!
执行结果表明,第二种方法同样实现了QQ号码的验证功能。下面我们再来演示如何通过正则表达式来对QQ号码进行验证,并将常规方法和正则表达式进行对比。
在给出演示代码以前,我们首先介绍一个用于匹配字符串内容的String类方法——matches:
public boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。该方法的参数名regex就是字符串形式的正则表达式,那么matches方法就通过读取这一表达式的含义,来判断调用该方法的字符串内容符合该表达式所规定的规则。
代码3:
public static void isQQCount3(String qqNumber)
{
//定义正则表达式
String regex = "[1-9][0-9]{4,14}";
//使用以上正则表达式对目标QQ号码字符串进行匹配
boolean flag = qqNumber.matches(regex);
if(flag)
System.out.println(qqNumber+":该QQ号码通过验证!");
else
System.out.println(qqNumber+":该QQ号码格式错误!");
}
9008:该QQ号码格式错误!
1895828587923545452:该QQ号码格式错误!
0123456:该QQ号码格式错误!
458b16m54:该QQ号码格式错误!
567890123:该QQ号码通过验证!
代码说明:
我们对代码3中出现的正则表达式含义进行一个简单的解释。首先正则表达式是按照字符串的位置来进行判断的。第一对方括号“[1-9]”表示,第一位字符串必须是在1-9之间的数字;第二对方括号“[0-9]”表示,第二位字符串必须是在0-9之间的数字;紧接着的一对大括号“{4,14}”表示第2到第15位字符串要重复使用第二对方括号的规则进行判断。
对比前两种方法和第三种方法,从代码量的角度来说,显然通过正则表达式对字符串QQ号码进行验证是最为方便的。由此可以非常明显的表明正则表达式在字符串操作方面的优势。
2 正则表达式的定义规则
那么通过对以上需求的演示可知,正则表达式的作用,就是用一些特定的符号来代表一些代码操作,而不必显示地将这些代码写出来,因此起到了简化代码书写的作用。那么我们学习正则表达式,其实就是在学习一些特殊符号的使用,通过这些符号的组合来,来达到对字符串进行操作的目的。下面我们就来介绍正则表达式中常用的一些符号。在标准类库java.util.regex包Pattern类的API文档中,详细介绍了正则表达式中常用的符号,这里我们对这些字符作一个简单的介绍,并在第二节《正则表达式的功能》中来演示这些符号的用法。
(1) 字符
实际就是我们常用的基本常见字符,比如代码制表符的“\t”、代表换行符的“\n”、代表回车符的“\r”等等,因此这里不再赘述。
(2) 字符类
字符类代表了专门用于操作字符的符号。书写规则为:一对方括号“[]”内包含了一些字符,表示字符串中某一位字符必须包含在这一对方括号内的字符范围内,比如a-z表示小写字母范围,A-Z表示大写字母范围,0-9表示数字范围。因为字符类的定义规则较为简单,因此请大家自行查阅JavaAPI文档。
(3) 预定义字符类
这一类字符通常通过一个字符来代表一个字符范围。这里有一个需要注意的地方,以表示数字的“\d”为例。在字符串中,单个出现的“\”表示转义字符,它可以将后面的字符串转义为其他含义,而“\”本身不包含在字符串内容中。但是预定义字符“\d”是一个整体,也就是说,“\”+“d”才能表示任意数字。因此在字符串形式的正则表达式中,若要使用“\d”则必须在其前面再加一个“\”,将后一个“\”转义为反斜线,而不是转义字符,因此表达式为“\\d”。
(4) 数量词
由以上所述内容可知,一对方括号,或者一个预定义字符,只能用于匹配一个字符串位上的字符。如果为了定义若干位置上的字符,则需要重复定义多个方括号对,或者预定义字符,将导致整个正则表达式非常冗长。因此可以在匹配一个字符的表达式后接数量词,表示后面的字符将重复匹配这一个规则,使得正则表达式更为简洁。
(5) 组
假如我们要匹配重复出现的字符,比如“kkk”,“qq”,“***”,“##”等等时,该如何去定义正则表达式呢?如果第一位使用“.”去匹配任意字符,那么第二位以及以后的字符位上的字符该如何保证和第一位一样呢?此时显然不能再使用“.”了,因为它同样表示任意字符,而不是对第一个匹配字符的引用。此时我们就可以使用组的概念。
将某一位规则用一对“()”括起来,表示一个组。当这一组所表示的字符匹配以后,通过引用这一组,即可继续向后匹配相同的字符。引用的方法为,在组后面加“\”+数字的形式,比如“\1”表示第一组,“\0”第0组,代表整个正则表达式。当然,由于转义字符的问题,还应该在“\1”前再加一个“\”。
引入组的概念以后,慢慢体现出了正则表达式规则的复杂性,这也就是它的弊端。
小知识点1:
当组的定义非常复杂时,比如JavaAPI文档中的例子——“((A)(B(C)))”,该如何去判断组的个数呢?窍门就是数左括号“(”的个数,例子中共有4个左括号,对应有4个组。按照左括号从做到右的顺序,“((A)(B(C)))”为第一组,“(A)”为第二组,“(B(C))”为第三组,“(C)”为第四组。
(6) 边界匹配器
若从一个英文语句中获取匹配某个正则表达式规则的单词时,就需要用到单词边界——“\b”。由于英文语句的单词都是通过空格进行分隔,因此表示某个规则的正则表达式前后分别添加单词边界“\b”即可将某个单词从英文语句字符串中取得。其他边界匹配器,请大家参考正则表达式API文档。