一、JDK提供的正则表达式

Java's java.util.regex 包包括:Pattern类、Matcher类、MatchResult接口和PatternSyntaxException异常:

  • Pattern 对象代表了编译了的正则表达式(A compiled representation of a regular expression.)。
  • Matcher ,An engine that performs match operations on a character sequence by interpreting a Pattern.
  • MatchResult接口:The result of a match operation.
  • PatternSyntaxException对象,描述非法的regex patterns,Unchecked exception thrown to indicate a syntax error in a regular-expression pattern.

 同时还需要了解是CharSequence,JDK 1.4定义的新的接口,它提供了String和StringBuffer这两个类的字符序列的抽象

interface CharSequence {

charAt(int i);

length();

subSequence(int start, int end);

toString();

}

为了实现这个新的CharSequence接口,String,StringBuffer以及CharBuffer都作了修改,很多的正则表达式的操作都要拿CharSequence作参数。

 

Matcher类的几个重要的方法:

  • boolean matches():Pattern匹配整个字符串
  • boolean lookingAt():Pattern匹配字符串的开头
  • boolean find():发现CharSequence里的,与pattern相匹配的多个字符序列
    boolean find(int start):告诉方法从参数start位置开始查找

group的概念

Group是指里用括号括起来的,能被后面的表达式调用的正则表达式。

Group 0 表示整个表达式,group 1表示第一个被括起来的group,以此类推。所以;

A(B(C))D

里面有三个group:group 0是ABCD, group 1是BC,group 2是C。

你可以用下述Matcher方法来使用group:

  • public int groupCount( )返回matcher对象中的group的数目。不包括group0。
  • public String group( ) 返回上次匹配操作(比方说find( ))的group 0(整个匹配)
  • public String group(int i)返回上次匹配操作的某个group。如果匹配成功,但是没能找到group,则返回null。
  • public int start(int group)返回上次匹配所找到的group的开始位置。
  • public int end(int group)返回上次匹配所找到的group的结束位置,最后一个字符的下标加一。
  • split( ) 是指将以正则表达式为界,将字符串分割成String数组,如果带有limit参数,split( )会限定分割的次数。
  • replaceFirst(String replacement)将字符串里,第一个与模式相匹配的子串替换成replacement。
  • replaceAll(String replacement),将输入字符串里所有与模式相匹配的子串全部替换成replacement。
  • appendReplacement(StringBuffer sbuf, String replacement)对sbuf进行逐次替换,而不是像replaceFirst( )或replaceAll( )那样,只替换第一个或全部子串。这是个非常重要的方法,因为replacement(replaceFirst( )和replaceAll( )只允许用固定的字符串来充当replacement)。有了这个方法,你就可以编程区分group,从而实现更强大的替换功能。
  • 调用完appendReplacement( )之后,为了把剩余的字符串拷贝回去,必须调用appendTail(StringBuffer sbuf, String replacement)。

 

使用group可以做到逐步缩小匹配的范围,直至精确匹配的目的。

start( )和end( ):如果匹配成功,start( )会返回此次匹配的开始位置,end( )会返回此次匹配的结束位置,即最后一个字符的下标加一。如果之前的匹配不成功(或者没匹配),那么无论是调用start( )还是end( ),都会引发一个IllegalStateException。

 

二、4大使用方法:

查询

String str="abc efg ABC";

 

String regEx="a|f"; //表示a或f

 

Pattern p=Pattern.compile(regEx);

 

Matcher m=p.matcher(str);

 

boolean rs=m.find();

 

如果str中有regEx,那么rs为true,否则为flase。如果想在查找时忽略大小写,则可以写成Pattern p=Pattern.compile(regEx,Pattern.CASE_INSENSITIVE);

 

 

提取

String regEx=".+\\\\(.+)$";

 

String str="c:\\dir1\\dir2\\name.txt";

 

Pattern p=Pattern.compile(regEx);

 

Matcher m=p.matcher(str);

 

boolean rs=m.find();

 

for(int i=1;i<=m.groupCount();i++){

 

System.out.println(m.group(i));

 

}

 

以上的执行结果为name.txt,提取的字符串储存在m.group(i)中,其中i最大值为m.groupCount();

 

 

分割

 

String regEx="::";

 

Pattern p=Pattern.compile(regEx);

 

String[] r=p.split("xd::abc::cde");

 

执行后,r就是{"xd","abc","cde"},其实分割时还有跟简单的方法:

 

String str="xd::abc::cde";

 

String[] r=str.split("::");

 

 

替换或者删除

String regEx="a+"; //表示一个或多个a

 

Pattern p=Pattern.compile(regEx);

 

Matcher m=p.matcher("aaabbced a ccdeaa");

 

String s=m.replaceAll("A");

 

结果为"Abbced A ccdeA"

 

如果写成空串,既可达到删除的功能,比如:

 

String s=m.replaceAll("");

 

结果为"bbced ccde"

 

 

三、一个实际的例子

下面的函数是一个实际应用的例子,需求是需要将抓取的网页中的<img src=''http://aa.bb.cc.com/images/**.jpg"> 中的地址,保存到一个地址列表中(以供抓取图片使用),并将绝对地址替换成本地的相对地址,即<img src="images/**.jpg">。

public static Map<String, String> getImagesURL(String description) {

      Map<String, String> map = new HashMap<String, String>();
        // img 的正则表达式:匹配<img>标签       
        String imgPattern = "<\\s*img\\s+([^>]+)\\s*>";
        Pattern pattern1 = Pattern.compile(imgPattern, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern1.matcher(description);

        // img src元素的正则表达式:匹配img标签内的src属性
        String srcPattern = "\\s*src\\s*=\\s*\"([^\"]+)\\s*\"";
        Pattern pattern2 = Pattern.compile(srcPattern, Pattern.CASE_INSENSITIVE);

        while (matcher.find()) {

           //group()返回符合表达式的内容
            Matcher matcher2 = pattern2 .matcher(matcher.group());
            // 一定要find(),这是实际的匹配动作
            if (matcher2.find()) {
                String src = matcher2.group();
                log.info(src);
                int i2 = src.lastIndexOf('/');
                int i1 = src.indexOf("http");
                if (i1 != -1) {
                    map.put(src.substring(i2 + 1, src.length() - 1), src
                            .substring(i1, src.length() - 1));
                }
            }
        }
        log.debug("图片:" + map);
        return map;
    }

 

整体思路是先匹配到img标签,然后匹配src属性,并修改src的属性

"<\\s*img\\s+([^>]+)\\s*>" 的解释:

\\s* :0 或多个空格   \\s+: 至少一个空格

([^>]+):指的是非>的所有的字符,至少一个

 

常用的正则表达式(参考jdk的regex文档)

字符集类

.                            表示任意一个字符

[abc]                     表示字符a,b,c中的任意一个(与a|b|c相同)

[^abc]                   除a,b,c之外的任意一个字符(否定)

[a-zA-Z]                从a到z或A到Z当中的任意一个字符(范围)

[abc[hij]]              a,b,c,h,i,j中的任意一个字符(与a|b|c|h|i|j相同)(并集)

[a-z&&[hij]]          h,i,j中的一个(交集)

\s                          空格字符(空格键, tab, 换行, 换页, 回车)

\S                         非空格字符([^\s])

\d                         一个数字,也就是[0-9]

\D                         一个非数字的字符,也就是[^0-9]

\w                        一个单词字符(word character),即[a-zA-Z_0-9]

\W                       一个非单词的字符,[^\w]

字符类:

B                         字符B

\xhh                    16进制值0xhh所表示的字符

\uhhhh                16进制值0xhhhh所表示的Unicode字符

\t                         Tab

\n                        换行符

\r                         回车符

\f                        换页符

\e                       Escape

逻辑运算符

XY                      X 后面跟着 Y

X|Y                    X或Y

(X)                     一个"要匹配的组(capturing group)". 以后可以用\i来表示第i个被匹配的组。

边界匹配符

^                      一行的开始

$                      一行的结尾

\b                    一个单词的边界

\B                    一个非单词的边界

\G                   前一个匹配的结束

 

数量表示符

"数量表示符(quantifier)"的作用是定义模式应该匹配多少个字符。

  • Greedy(贪婪的): 除非另有表示,否则数量表示符都是greedy的。Greedy的表达式会一直匹配下去,直到匹配不下去为止。(如果你发现表达式匹配的结果与预期的不符),很有可能是因为,你以为表达式会只匹配前面几个字符,而实际上它是greedy的,因此会一直匹配下去。
  • Reluctant(勉强的): 用问号表示,它会匹配最少的字符。也称为lazy, minimal matching, non-greedy, 或ungreedy。
  • Possessive(占有的): 目前只有Java支持(其它语言都不支持)。它更加先进,所以你可能还不太会用。用正则表达式匹配字符串的时候会产生很多中间状态,(一般的匹配引擎会保存这种中间状态,)这样匹配失败的时候就能原路返回了。占有型的表达式不保存这种中间状态,因此也就不会回头重来了。它能防止正则表达式的失控,同时也能提高运行的效率。

 

Greedy                    Reluctant                           Possessive                      匹配

 

X?                            X??                                      X?+                                  匹配一个或零个X

X*                            X*?                                      X*+                                  匹配零或多个X

X+                           X+?                                      X++                                 匹配一个或多个X

X{n}                        X{n}?                                   X{n}+                               匹配正好n个X

X{n,}                       X{n,}?                                 X{n,}+                              匹配至少n个X

X{n,m}                   X{n,m}?                                X{n,m}+                           匹配至少n个,至多m个X

 

 

参考资料

要想进一步学习正则表达式,建议看Mastering Regular Expression, 2nd Edition,作者Jeffrey E. F. Friedl (O’Reilly, 2002)。

"Regular Expressions and the Java Programming Language," Dana Nourie and Mike McCloskey (Sun Microsystems, August 2002) presents a brief overview of java.util.regex, including five illustrative regex-based applications:
http://developer.java.sun.com/developer/technicalArticles/releases/1.4regex/

http://www.blogjava.net/beike/archive/2006/04/28/43832.html 

http://wcjok.bokee.com/4293762.html