日期:2014-05-20  浏览次数:20970 次

【分享】正则平衡组应用场景分析及性能优化
为了获得更好的阅读效果,可以到我的博客查看
.NET正则基础之——平衡组
 
声明一:本帖不是散分帖,只对找出错误,提供改进建议,进行技术讨论,阅读后给出个人见解的回复给分,其余回复不给分,请尽量看过帖子后再回复。

声明二:本帖给出的只是一些方法和思路,不是模板,我也一直不推荐把正则套模板来用。对于部分实现,认为不适合用正则来解决的朋友,请给出更优的实现,不要只是泛泛的说“这个不适合用正则来实现”。

声明三:本帖可能比较长,主要是因为偏重应用场景分析的缘故,如果对正则和平衡组感兴趣,可以先收起来慢慢看^_^

1 概述

平衡组是微软在.NET中提出的一个概念,主要是结合几种正则语法规则,提供对配对出现的嵌套结构的匹配。.NET是目前对正则支持最完备、功能最强大的语言平台之一,而平衡组正是其强大功能的外在表现,也是比较实用的文本处理功能,目前只有.NET支持,相信后续其它语言会提供支持。
平衡组可以有狭义和广义两种定义,狭义平衡组指.NET中定义的(?<Close-Open>Expression)语法,广义平衡组并不是固定的语法规则,而是几种语法规则的综合运用,我们平时所说的平衡组通常指的是广义平衡组。本文中如无特殊说明,平衡组这种简写指的是广义平衡组。
正是由于平衡组功能的强大,所以带来了一些神秘色彩,其实平衡组并不难掌握。下面就平衡组的匹配原理、应用场景以及性能调优展开讨论。

2 平衡组匹配原理
2.1 预备知识

平衡组通常是由量词,分支结构,命名捕获组,狭义平衡组,条件判断结构组成的,量词和分支结构这里不做介绍,这里只对命名捕获组,狭义平衡组和条件判断结构做下说明。

2.1.1 命名捕获组

语法:(?<name>Expression)  
 (?’name’Expression)
以上两种写法在..NET中是等价的,都是将“Expression”子表达式匹配到的内容,保存到以“name”命名的组里,以供后续引用。
对于命名捕获组的应用,这里不做重点介绍,只是需要澄清一点,平时使用捕获组时,一般反向引用或Group对象使用得比较多,可能会有一种误解,那就是捕获组只保留一个匹配结果,即使一个捕获组可以先后匹配多个子串,也只保留最后一个匹配到的子串。但事实是这样吗?
举例来说:
源字符串:abcdefghijkl
正则表达式:(?<chars>[a-z]{2})+
命名捕获组chars最终捕获的是什么?
C# code
string test = "abcdefghijkl";
Regex reg = new Regex(@"(?<chars>[a-z]{2})+");
Match m = reg.Match(test);
if (m.Success)
{
      richTextBox2.Text += "匹配结果:" + m.Value + "\n";
      richTextBox2.Text += "Group:" + m.Groups["chars"].Value + "\n";
}
//输出
匹配结果:abcdefghijkl
Group:kl
从m.Groups["chars"].Value的输出上看,似乎确实是只保留了一个匹配内容,但却忽略了一个事实,Group实际上是Capture的一个集合
C# code
string test = "abcdefghijkl";
Regex reg = new Regex(@"(?<chars>[a-z]{2})+");
Match m = reg.Match(test);
if (m.Success)
{
     richTextBox2.Text += "匹配结果:" + m.Value + "\n";
     richTextBox2.Text += "Group:" + m.Groups["chars"].Value + "\n--------------\n";
     foreach (Capture c in m.Groups["chars"].Captures)
     {
           richTextBox2.Text += "Capture:" + c + "\n";
     }
}
//输出
匹配结果:abcdefghijkl
Group:kl
--------------
Capture:ab
Capture:cd
Capture:ef
Capture:gh
Capture:ij
Capture:kl
平时应用时可能会忽略这一点,因为很少遇到一个捕获组先后匹配多个子串的情况,而在一个捕获组只匹配一个子串时,Group集合中就只有一个Capture元素,所以内容是一样的。
C# code
string test = "abcdefghijkl";
Regex reg = new Regex(@"(?<chars>[a-z]{2})");
Match m = reg.Match(test);
if (m.Success)
{
     richTextBox2.Text += "匹配结果:" + m.Value + "\n";
     richTextBox2.Text += "Group:" + m.Groups["chars"].Value + "\n--------------\n";
     foreach (Capture c in m.Groups["chars"].Captures)
     {
          richTextBox2.Text += "Capture:" + c + "\n";
     }
}
//输出
匹配结果:ab
Group:ab
--------------
Capture:ab
捕获组保存的是一个集合,而不只是一个元素,这一知识点对于理解平衡组的匹配原理是有帮助的。

2.1.2 狭义平衡组

语法:(?<Close-Open>Expression)
其中“Close”是命名捕获组的组名,也就是“(?<name>Expression)”中的“name”,可以省略,通常应用时并不关注,所以一般都是省略的,写作“(?<-Open>Expression)”。作用就是当此处的“Expression”子表达式匹配成功时,则将最近匹配成功到的命名为“Open”组出栈,如果此前不存在匹配成功的“Open”组,那么就报告“(?<-Open>Expression)”匹配失败,整个表达式在这一位置也是匹配失败的。

2.1.3 条件判断结构

语法:(?(Expression)yes|no)
  (?(name)yes|no)
对于“(?(Expression)yes|no)”,它是“(?(?=Expression)yes|no)”的简写形式,相当于三元运算符
(?=Expression) ? yes : no
表示如果子表达式“(?=Expression)”匹配成功,则匹配“yes”子表达式,否则匹配“no”子表达式。如果“Expression”与可能出现的命名捕获组的组名相同,为避免混淆,可以采用“(?(?=Expression)yes|no)”方式显示声明“Expression”为子表达式,而不是捕获组名。
“(?=Expression)”验证当前位置右侧是否能够匹配“Expression”,属于顺序环视结构,是零宽度的,所以它只参与判断,即使匹配成功,也不会占有字符。