正则表达式

摘要:正则表达式主要用途是文本规则的匹配和验证,在处理字符串的程序或网页时,经常需要对用户输入进行正则验证,从而简化复杂的程序编写逻辑,因此,正则表达式实际上就是一种文本规则的验证工具。

引例

在正则表达式诞生之前,通常我们对某一字符串文本进行验证的时候,需要编配大量的匹配规则,尤其出现在需要用户输入的地方,比如对用户输入的用户名,密码和邮箱进行输入验证,如果存在非法字符,则可能认为是恶意输入。下面通过Java语言介绍一个没有使用正则表达式的邮箱输入验证案例。

给定一个邮箱:aB-c_d.ef@g1_2-3h.i4_J-k, 对于该邮箱(任意邮箱),我们将其划分为3个部分,首先,邮箱一定至少包含一个”.”号且只包含一个”@”符号,以这两个符号将邮箱分成3部分,第一部分为@之前的内容,可以是任意字母,数字,中划线,下划线,点号;第二部分为@与最后一个点号之间的内容,可以是任意字母,数字,中划线,下划线,但不可以是点号;第三部分为最后一个点号一直到结尾处的内容,实际上应该是域名,为了简化,我们认为此处内容也可以是任意字母,数字,下划线,中划线,但不可以是点号。

明白以上规则之后,见代码:

public class TestRegexp {
    public static void main(String[] args) {
    	String emailStr = "csuzhangvae@163.com";//[\w-\.]+@[\w-]+\.[\w-]+
    	if(regexVerify(emailStr)){
            if(verifyDNS(emailStr)){
                System.out.println("email is right");
            }else{
                System.out.println("email is wrong");
            }
        }
    }

    public static boolean isDigit(char c){
    	if(c >= '0' && c <= '9'){
    		return true;
    	}else{
    		return false;
    	}
    }

    public static boolean isLetter(char c){
    	if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')){
    		return true;
    	}else{
    		return false;
    	}
    }

    public static boolean commVerify(String emailStr){
    	boolean flag = true;
    	if(emailStr.indexOf(".") == -1 || emailStr.indexOf("@") == -1){
    		flag = false;
    	}else{
    		int highIndex = emailStr.lastIndexOf(".");
    		String highStr = emailStr.substring(highIndex+1);
    		System.out.println(highStr);

    		char highChars[] = highStr.toCharArray();
    		for(int i=0;i<highChars.length;i++){
    			if(!isDigit(highChars[i]) && !isLetter(highChars[i]) && highChars[i] != '-' && highChars[i] != '_'){
    				flag = false;
    				break;
    			}
    		}

    		int middleIndex = emailStr.indexOf("@");
    		String middleStr = emailStr.substring(middleIndex+1,highIndex);
    		System.out.println(middleStr);

    		char middleChars[] = middleStr.toCharArray();
    		for(int i=0;i<middleChars.length;i++){
    			if(!isDigit(middleChars[i]) && !isLetter(middleChars[i]) && middleChars[i] != '-' && middleChars[i] != '_'){
    				flag = false;
    				break;
    			}
    		}
    		int lowIndex = 0;
    		String lowStr = emailStr.substring(0,middleIndex);
    		System.out.println(lowStr);

    		char lowChars[] = lowStr.toCharArray();
    		for(int i=0;i<lowChars.length;i++){
    			if(!isDigit(lowChars[i]) && !isLetter(lowChars[i]) && lowChars[i] != '-' && lowChars[i] != '_' && lowChars[i] != '.'){
    				System.out.println(lowChars[i]);
    				flag = false;
    				break;
    			}
    		}


    	}
    	return flag;
    }
}

以上代码虽然可以实现邮箱规则的匹配,但是各种条件语句和循环语句的嵌套导致结构不清晰,代码量多。

如何改用正则表达式呢。这便是本文要介绍的问题。

正则表达式

正则表达式是一种文本规则描述的匹配工具,其规则和语法非常多,也不需要全部记住,但对最基本的一定要牢记,最基本最简单的正则表达式就是文本字符串本身, 用于匹配与该字符串相等的字符串。

[]表示选择

表达式 描述
[abc] 表示取值可以是a,可以是b,也可以是c
[^abc] 表示取值不是abc中的任意字符,即除abc以外的内容
[a-zA-Z] 表示所有字母,大写和小写,[a-z]表示小写字母,[A-Z]表示大写字母

常用元字符

表达式 描述
. 除换行符以外的任意字符
\d 所有数字
\w 所有字母,数字,下划线
\s 所有空白字符
\b 单词边界位置
^ 字符串开始
$ 字符串结尾

常用的反义字符

表达式 描述
\D 非数字
\W 非字母、数字、下划线
\S 非空白字符
\B 非单词开始或结束边界位置

表示出现的次数

表达式 描述
X? 表示出现0次或1次
X* 表示出现0次或多次
X+ 表示出现1次或多次
X{n} 表示出现n次
X{n,} 表示出现的长度大于n次
X{n,m} 表示出现n到m次

关系运算

表达式 描述
X或Y 表示要么是X的正则,要么是Y的正则
(X) 表示一组规范,一个分组

匹配策略

介绍了以上的正则表达式及其表示含义之后,大家都或多或少了解了一些相关知识,现在顺便介绍一下正则表达式的贪婪匹配和惰性匹配策略。

一般匹配都是遵循贪婪匹配策略,即尽可能多地匹配,而以下规则支持惰性匹配,即尽可能少地匹配。

表达式 描述
*? 重复任意次,但尽可能少地重复
*? 重复任意次,但尽可能少地重复
+? 重复1次或多次,但尽可能少地重复
?? 重复0次或1次,但尽可能少地重复
{n,m}? 重复n到m次,尽可能少地重复
{n,}? 重复至少n次,但尽可能少地重复

正则验证

下面就上面介绍地邮箱验证的例子,我们给出使用正则表达式进行规则匹配的案例,对比之下,可以发现,使用正则表达式,不但能使程序结构清晰,也大大简化了代码量和我们的逻辑判断工作。

JAVA对正则表达式有着良好地支持,自JDK1.4,它提供了java.util.regex包,主要负责正则匹配的两个类为Pattern类和Matcher类,Pattern根据指定正则表达式字符串构建匹配模式,并利用matcher方法返回Matcher类对象实例,用于完成匹配,实际的匹配是交给Matcher完成的,Pattern类对象的构造是通过静态方法compile构造。

public static boolean regexVerify(String emailStr){
     Pattern p = Pattern.compile("[\\w-\\.]+@[\\w-]+\\.[\\w-]+");
     Matcher m = p.matcher(emailStr);
     if(m.matches())
    	//if(emailStr.matches("[\\w-\\.]+@[\\w-]+\\.[\\w-]+"))//String object has a good support to regexp
      	return true;
     else
      	return false;
}

从以上代码可以看出,使用Java的正则表达式可以快速完成与大量逻辑判断相同的工作,且结构清晰,只要定义的正则表达式正确,其实在Java中一般不直接使用Pattern类和Matcher类完成正则模式匹配,而使用String自带的正则支持。因为正则匹配的对象都是文本字符串,目的就是为了验证文本规则,而String在Java中又是一个及其特殊的类,因此自从JDK1.4,引进正则表达式,String类就作了相应的修改,提供了split,matches,replaceAll,replaceFirst等方法。如上述代码注释的内容即为直接使用String对象的matches方法,其效果是完全一样的。

另外我想延伸的一点是,上述程序代码即正则表达式规则,虽然能够判别邮箱,但是邮箱通过的域名都是有限的(如163.com,qq.com),因此,本文提供精确的邮箱认证方式,即登录到邮箱服务器,验证邮箱是否存在,并返回验证结果。

该功能需要Appache 的common-net.jar和dnsjava.jar包的支持。

public static boolean verifyDNS(String emailStr) {
    String host = "";
    String hostName = emailStr.split("@")[1];
    Record[] result = null;
    SMTPClient client = new SMTPClient();
    
    try {
        // 查找MX记录
        Lookup lookup = new Lookup(hostName, Type.MX);
        lookup.run();
        if (lookup.getResult() != Lookup.SUCCESSFUL) {
            return false;
        } else {
            result = lookup.getAnswers();
        }
    
        // 连接到邮箱服务器
        for (int i = 0; i < result.length; i++) {
            host = result[i].getAdditionalName().toString();
            client.connect(host);
            if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) {
                client.disconnect();
                continue;
            } else {
                break;
            }
        }
    
        client.login("xxxxxxxxxxxxxxxz.com");
        client.setSender("xxxxxxxxxxxx@xxxxxxxxxxxxxxx.com");
    
        client.addRecipient(emailStr);
        if (250 == client.getReplyCode()) {
            return true;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            client.disconnect();
        } catch (Exception e) {
        }
    }
    return false;
}

这部分代码拷贝至http://www.eziep.net/details/80.html。以下是常见的正则匹配规则,参考自网上相关博客: