正则表达式的环视和匹配

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了正则表达式的环视和匹配相关的知识,希望对你有一定的参考价值。


环视

  环视匹配的最终结果就是一个位置,有四种类型的环视:

  (?=Expression) 顺序肯定环视,表示所在位置右侧能够匹配Expression

  (?!Expression) 顺序否定环视,表示所在位置右侧不能匹配Expression

  (?<=Expression) 逆序肯定环视,表示所在位置左侧能够匹配Expression

  (?<!Expression) 逆序否定环视,表示所在位置左侧不能匹配Expression


  可以用以下两个正则表达式理解环视:

  (1)字母、数字、特殊符号全部出现,至少8位

Pattern pattern = Pattern.compile("^(?=.*[\\d]+)(?=.*[a-zA-Z]+)(?=.*[#$_!]+)[\\w#$!]{8,}$");
Matcher matcher = pattern.matcher("123456a890#");
while (matcher.find()) {
    System.out.println(matcher.group());
}

  表达式的意思是开头位置应该是这样一个位置,后面有数字、字母、特殊符号,然后后面有至少8个指定的字符。

 (2)字母、数字、特殊符号至少出现两种,6-20位

Pattern pattern = Pattern.compile("^(?![\\d]+$)(?![a-zA-Z]+$)(?![!#$%^&*]+$)[\\da-zA-Z!#$%^&*]{6,20}$");
Matcher matcher = pattern.matcher("aaaaaaaaaaa!");
while (matcher.find()) {
    System.out.println(matcher.group());
}

  表达式的意思是开头和结尾中间不能全是数字,不能全是字母,不能全是特殊符号,然后是指定的字符出现6到20次。


三种匹配方式

  匹配优先(贪婪匹配)

  忽略优先(懒惰匹配)

  占有优先


  理解这三种方式的关键在于理解回溯,下面的代码辅助理解这三种方式:

    public static void main(String[] args)
    {
        Pattern pattern = Pattern.compile("a.*a");
        Matcher matcher = pattern.matcher("ababa");
        if (matcher.find())
        {
            System.out.println(matcher.group());
        }
        else
        {
            System.out.println("不匹配");
        }

        pattern = Pattern.compile("a.*?a");
        matcher = pattern.matcher("ababa");
        if (matcher.find())
        {
            System.out.println(matcher.group());
        }
        else
        {
            System.out.println("不匹配");
        }

        pattern = Pattern.compile("a.*+a");
        matcher = pattern.matcher("ababa");
        if (matcher.find())
        {
            System.out.println(matcher.group());
        }
        else
        {
            System.out.println("不匹配");
        }
    }

输出:

ababa
aba
不匹配


固化分组和占有匹配

  下面的文字和例子取值《精通正则表达式》

  假设我们有这样的问题,把类似 3.690000023 的小数保留两位小数,类似 2.3563895 的小数保留三位小数,也就是说如果小数的第三位是0,则保留两位小数,如果是非0,就保留三位小数。

$number =~ s/(\.\d\d[1-9]?)\d*/$1/;

  这个表达式完全可以工作,美中不足的一点是,当 $number 类似 3.695 的时候,我们把 .695 替换为了 .695,浪费了工夫。为了解决这个问题,我们把表达式稍稍修改一下。

$number =~ s/(\.\d\d[1-9]?)\d+/$1/;

  仅仅把星号替换成了加号,这样表示括号外至少有一位数字的时候才进行替换。看上去很完美,但是却出现了致命的错误,.695 被替换成了 .69了。这是为什么呢?在表达式 (\.\d\d[1-9]?) 匹配了 .695 后, 后面的 \d+ 无法匹配了,为了使整个表达式匹配成功,引擎必须回溯,[1-9]?必须把匹配的数字吐出去,所以 5 被 \d+ 匹配了。这就是回溯造成的问题。事实上在这种情况下,我们不希望引擎回溯,有两种办法可以强迫引擎放弃回溯,固化分组和占有量词。

$number =~ s/(\.\d\d(?>[1-9]?))\d+/$1/; # 固化分组  

$number =~ s/(\.\d\d[1-9]?+)\d+/$1/;   # 占有量词

  引擎放弃回溯后, 上面的表达式将无法匹配 .695,这正是我们想要的。

  占有优先量词与匹配优先量词很相似,只是它们从来不交还已经匹配的字符。

  你也许会想,占有优先量词和固化分组关系非常紧密。像「\w++」这样的占有优先量词与「(?>\w+)」的匹配结果完全相同,只是写起来更加方便而已。使用占有优先量词,「(\.\d\d(?>[1-9]?))\d+」写做「(\.\d\d[1-9]?+)^\d+」。

  请务必区分「(?>M)+」和「(?M+)」。前者放弃「M」创建的备用状态,因为「M」不会制造任何状态,所以这样做没什么价值。而后者放弃「M+」创造的未使用状态,这样做显然有意义。

  比较「(?>M)+」和「(?>M+)」,显然后者就对应于「M++」,但如果表达式很复杂,例如

(\\"|[^"])*+

  从占有优先量词转换为固化分组时,大家往往会想到在括号中添加‘?>’得到 (?>\\"|[^"])*。这个表达式或许有机会实现你的目的,但它显然不等于那个使用占有优先量词的表达式;它就好像是把「M++」写作「(?>M)+」一样。正确的办法是,去掉表示占有优先的加号,用固化分组把余下的部分包括起来:

(?>(\\"|[^"])*)

  上面的过程可以使用下面的代码验证:

    public static void main(String[] args)
    {
        String[] datas = { "1.234001", "1.234", "1.230", "1.23" };
        Pattern pattern = Pattern.compile("(\\.\\d\\d[1-9]?)\\d*");
        for (String data : datas)
        {
            Matcher matcher = pattern.matcher(data);
            if (matcher.find())
            {
                System.out.println(matcher.group(1));
            }
            else
            {
                System.out.println("不匹配");
            }
        }
        
        System.out.println("========");
        
        pattern = Pattern.compile("(\\.\\d\\d[1-9]?)\\d+");
        for (String data : datas)
        {
            Matcher matcher = pattern.matcher(data);
            if (matcher.find())
            {
                System.out.println(matcher.group(1));
            }
            else
            {
                System.out.println("不匹配");
            }
        }
        
        System.out.println("========");

        pattern = Pattern.compile("(\\.\\d\\d(?>[1-9]?))\\d+");
        for (String data : datas)
        {
            Matcher matcher = pattern.matcher(data);
            if (matcher.find())
            {
                System.out.println(matcher.group(1));
            }
            else
            {
                System.out.println("不匹配");
            }
        }
        
        System.out.println("========");
        
        pattern = Pattern.compile("(\\.\\d\\d(?>[1-9]?+))\\d+");
        for (String data : datas)
        {
            Matcher matcher = pattern.matcher(data);
            if (matcher.find())
            {
                System.out.println(matcher.group(1));
            }
            else
            {
                System.out.println("不匹配");
            }
        }
    }

输出

.234
.234
.23
.23
========
.234
.23
.23
不匹配
========
.234
不匹配
.23
不匹配
========
.234
不匹配
.23
不匹配


其他参考资料

《[精华] 正则表达式30分钟入门教程》

http://www.oschina.net/question/12_9507


《千里之行始于足下》的博客

http://blog.csdn.net/shangboerds/article/category/1124700


《雁过无痕》的博客

http://blog.csdn.net/lxcnn/article/category/538256



本文出自 “自强不息,厚德载物” 博客,请务必保留此出处http://wangzhichao.blog.51cto.com/2643325/1747621

以上是关于正则表达式的环视和匹配的主要内容,如果未能解决你的问题,请参考以下文章

巧解正则表达式环视

正则基础之 环视

深入理解正则表达式环视的概念与用法

正则之环视

js 正则表达式之环视结构

环视是不是会影响正则表达式可以匹配哪些语言?