欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

正则表达式学习笔记

程序员文章站 2022-06-26 12:07:31
...

正则表达式学习笔记

最近做了一道算法题,题目是提供一个函数来去除Java源文件中所有注释
拿到题目开始觉得挺简单的,不就是多行注释和单行注释么,一行一行分别处理一下就行了:

public String removeComments(String sourceCode){
    String lines = sourceCode.split("\\n");
    String result = "";
    String trimLine = "";
    boolean multiLine = false;
    for(String line:lines){
        trimLine = line.replaceAll("\\s","");
        if(multiLine){
            if(line.endsWith("*/")){
                multiLine = false;
            }
            continue;
        }
        if(line.startsWith("/*")){
            multiLine = true;
            continue;
        }
        if(trimLine.startsWith("//")){
            continue;
        }
        if(trimLine.length()>0){
            result+="\\n";
        }
        result += line;
    }
    return result;
}

分析

稍微分析一下就发现这段代码错漏百出:
- /*xxxxx*/这样的注释就会导致multiLine永远为true
- 在正常的代码行末尾也能加上//xxxxx这样的注释,无法处理
- 另外也需要考虑出现在字符串中的注释标识,比如System.out.println(“//xxxxx/*xxxx*/”)就不应该被去除
- 其他bug…

分析到这里已经知道这题可以给自己0分了,还是先去研究清楚Java中一共会出现那些注释情况,已经那些可能被误伤的字符串。

/**
 * 多行注释
 * @param args
 */

if(a==0/*单行的多行注释*/)

//普通单行注释

int i=0; //行尾的单行注释

String str1 = "https://www.qq.com"; //可能被误伤的字符串1
String str2 = "/*******/"; //可能被误伤的字符串2

另外经过查阅文档和在IDE里面尝试,得知:
在多行注释的开头/*和结尾*/之间不允许出现其他的*/

这么分析下来,这看似简单的题目还挺复杂的。逻辑弄清楚了,用if/else总能堆出来。不过,能不能用正则来实现呢?自己的正则水平实在是水得很,只能去google了。很快就在Stack Overflow找到答案,手动尝试了下确实满足所有的情况了。简单把帖子里面的正则用java写出来如下:

public String removeComments(String sourceCode){
    return sourceCode==null?null:sourceCode.replaceAll("((['\"])(?:(?!\\2|\\\\).|\\\\.)*\\2)|//[^\\n]*|/\\*(?:[^*]|\\*(?!/))*\\*/","$1");
}

自己写不出来这么优秀的正则,至少也要弄清楚这里面每一段的意思。


正则分析

下面是*中正则的逻辑结构图

正则表达式学习笔记

可以看到,第一条规则是用来匹配代码中的字符串常量的,第二条是用来匹配单行注释//的,第三条是用来匹配多行注释/**/的。

难易度第二条<第三条<第一条,从易到难开始分析。

匹配单行注释

//[^\\n]*

这个很简单

匹配两个/以及任意后面跟着出现的非换行字符。

匹配多行注释

/\\*(?:[^*]|\\*(?!/))*\\*/

这个用到了非捕获匹配?:expr和负向前瞻?!expr

  • (?:expr) 非捕获匹配,匹配expr但是不捕获匹配结果
  • (?=expr) 正向前瞻,断言紧跟着当前位置的的字符串匹配expr,不捕获匹配结果
  • (?<=expr) 正向后顾,断言当前位置之前的字符串匹配expr,不捕获匹配结果
  • (?!expr) 负向前瞻,断言紧跟着当前位置的的字符串不匹配expr,不捕获匹配结果
  • (?<!expr) 负向后顾,断言当前位置之前的字符串不匹配expr,不捕获匹配结果

了解了上述正则知识点以后,就可以解读这段正则了:

/\*匹配/*
[^*]匹配任意不包含*的字符串
\*(?!/)匹配*后面不是/的字符串
\*/匹配*/

匹配代码中的字符串

((['\"])(?:(?!\\2|\\\\).|\\\\.)*\\2)

这里用到了分组\2,也就是([‘\”])中匹配的内容,经过解读后如下:

(([‘\”])(?:(?!\2|\\).|\\.)*\2) 整段匹配分组为1
([‘\”]) 匹配’或”,分组为2
(?!\2|\\). 匹配当前位置往后不是单个出现的\或者分组2最近匹配到的结果
\\. 匹配\x,x为任意字符
\2 匹配最近在分组2最近匹配到的结果

这三段组合起来最后用分组1替换,就是正好去除单行和多行注释,同时保留了代码中的字符串。

详细的正则的每一步匹配过程可以用regex101的debug功能来看。