写正则表达式的正确姿势

前言

上一篇文章我们学习了正则表达式原理,这次我们学习下怎么写正则表达式。这里,我们不会学习正则表达式的各种符号,如果不经常写,那么肯定是会忘的。到时候如果要想写那么还需我们再去查资料。这里我们主要学习下在Java中如何使用正则,以及怎么写出高效的正则表达式。

在Java中使用正则

先来干的,说下在Java中如何使用正则表达式。

通常情况下,我们做一个字符串的分割匹配之类的可能就直接使用String方法自带的api来完成了。这里我们学习下正则表达式的实际使用流程,以及实际应用场景的使用。

在正则表达式原理中,我们知道对于一个正则表达式,计算机需要把这个正则表达式翻译成一个自动机。在Java中这个自动机就是:Pattern。所以第一步就是将正则编译成Pattern

Pattern p = Pattern.compile("regex");

然后我们用我们的目标字符串去匹配得到一个匹配结果。Java的正则原理是基于NFA的,支持的能力很全面。因此匹配结果中会有很多的信息。而这个结果在Java中就是Matcher,匹配器。这个匹配结果包含了我们想要的所有匹配信息

String target = "targetString";
Matcher m = p.matcher(target);

现在我们拿到了匹配结果,这时候就可以从匹配结果中找到我们想要的信息了。

  1. 判断target整串是否符合正则表达式
  boolean isMatch = m.matchers();
  1. 获取所有匹配的子串
  List<String> result = new ArrayList<String>();
  m.reset();
  while(m.find()) {
    result.add(target.subString(m.start(), m.end());
  }
  1. 判断target内是否有符合正则规则的子串
boolean hasMatch = m.lookingAt();
  1. 替换符合正则规则的子串
String replaceAll = m.replaceAll("xxx");
String replaceFirst = m.replaceFirst("xxx");

注意事项:

  • Matcher的matches(),lookingAt(),find()都会受到检索范围的影响。
  • find(int start),replaceAll(),replaceFirst(),reset()不受影响。
  • 另外Matcher的start和end属性是整个串的index,与检索范围无关。
  • 可以使用region(int start, int end)来确定要检索的范围。

Java中正则的数量词模式

我们知道Java的量词有*和+。那么数量词模式又是什么呢?

正则表达式原理中我们知道NFA在匹配的时候需要两个原则,1是量词的优先类型,2是后进先出(回溯路径)。因为量词的优先类型不同可能得到的匹配结果,和匹配的速度也不相同。
Java有三种数量词模式:Greedy(贪婪),Reluctant(懒惰),Possessive(独占)。
听名字可能不知道是干啥,我们来分别介绍下。

  1. 贪婪模式是默认的模式,正常使用数量词时就是贪婪模式,他会尽可能多的匹配字符串。说白了就是量词优先。
  2. 懒惰模式,量词后面加?。尽可能少的匹配被量词修饰的字符串。
  3. 独占模式,量词后面加+,与贪婪相同量词优先,尽可能多匹配字符串,但是不会回溯。

举个例子,对于正则表达式(".*")来说,三种表达如下:

        String target = "start \"hello world\" haha \"hello java\" 666 \"hello android\"";
        Pattern greedy = Pattern.compile("\".*\"");
        Pattern reluctant = Pattern.compile("\".*?\"");
        Pattern possessive = Pattern.compile("\".*+\"");

我们可以看到这个正则表达式的意思就是要匹配双引号以及双引号包含的内容。那么这三种写法的结果分别是什么呢?
greedy的匹配结果:"hello world" haha "hello java" 666 "hello android"
reluctant的匹配结果:"hello world", "hello java", "hello android"(三个结果)
possessive的匹配结果:

对于贪婪模式,尽可能多的匹配,因为是(.* ),所以最后的引号也会匹配到(.* )中,这时明显匹配失败了,这时就会回溯到上一个选择的路径就是对于引号的匹配是用(.*)还是(" ),这时选择(")匹配成功得到 "hello world" haha "hello java" 666 "hello android"

懒惰模式是尽可能少的匹配量词修饰的字符串,因此会得到三个符合要求的结果。

而独占模式就是匹配失败后不会产生回溯因此匹配结果是空。

高效的使用正则表达式

优化使用正则表达式主要是从两点出发,1是优化正则表达式本身,我们知道NFA型的正则表达式是以正则为主导的,正则表达式的写法不同对于匹配的速度还是有影响的。优化正则表达式本身的思路就是减少回溯,仅可能精准的匹配想要匹配的字符。2、使用过程的优化,工具本身带有的给正则的属性以及自身使用正则的习惯优化。

  1. 避免重新编译
String target0 = "target0";
String target1 = "target1";

//这样的写法"abc“这个正则会被编译两次
target0.matcher("abc");
target1.matcher("abc");

//"abc"只会被编译一次
Pattern p = Pattern.compline("abc”);
boolean isMatch0 = p.matcher(target0).matchers();
boolean isMatch1 = p.matcher(target1).matchers();
  1. 不要滥用括号,使用非捕获型括号
    Java中的括号表示捕获组的概念,捕获组在匹配时会有一些引用,造成一些额外的开销,因此能不用括号尽量不用括号。另外,当我们要使用括号表示一个组的时候,如果我们不需要引用括号内的文本,我们可以使用非捕获型括号:(?:)。

  2. 量词等价转换
    对java来说:\d\d\d\d要比\d{4}快(Perl、Python、PHP正好相反)
    ====比={4}要快(Perl、Ruby和.NET优化手段更高级,两者一样快)
    另外,使用正确的量词(+、*、?、{n,m}),如果能够限定长度,那就最好了。

  3. 不要滥用字符组
    如果像这样的字符组”[:]“,里面只有字符,那么这样程序就有处理字符组的代价。使用.和这样的原符号时也不要用[.][]来转义,我们应该使用转义符来转义,而不是用字符组。

  4. 使用边界匹配符
    使用正确的边界匹配符(^、$、\b、\B等),限定搜索字符串位置

  5. 避免过多回溯
    首先优化量词的使用,对于特定的业务场景,查看是需要先优先量词还是忽略量词,对于java来说就是使用贪婪模式还是使用懒惰模式。
    另外警惕过度回溯,比如”([abc]|[aef])+“这个正则表达式匹配”aaaaaaaaaaaaaa1"的时候耗时会非常的长,因为他在最后发现1匹配的时候,会回溯到上一个a,然后尝试用[aef]匹配,然后[aef]中有a,然后再去匹配1,发现不匹配,又回退到倒数第二个a,依次类推,回溯的次数变成了指数级。解决方式,第二个去掉a(会回溯一次),或者使用独占模式([abc][aef])++(不会产生回溯)。

  6. 使用工具验证
    检测修改,有时你认为的优化可能禁止了其他你不知道的已经生效的优化。或者你优化了正则,但是正确性却不存在了。附上一个调试正则表达式的网址:http://regex.zjmainstay.cn/

总结

正则表达式是个很强大的东西,虽然平时我们只是用了它皮毛,但是如果使用不当还是会遇到一些问题的。因此了解它的原理,知道如何正确使用它还是很有必要的。这里我们也只是大致学习了平时自己不了解的正则相关的知识,当然还有很多正则相关的内容这里是没有覆盖到的,感兴趣的同学可以去看精通正则表达式这本书。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容