Java替换字符串中的“\"”的处理方法(String.replaceAll的源码分析)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_17169663/article/details/78982493

假设有这样一个字符串:

[{"startDate":"2018-01-03 00:00:00","reason":"a\"","endDate":"2018-01-24 00:00:00","updateDate":"","version":"","id":"9999999","msgType":"","createDate":"2018-01-03 11:50:44"}]

要将其中reason字段的值中的“\"”替换为“--”,使用String.replaceAll(String regex, String replacement)函数,这应该这么做呢?

一开始,我的代码如下:

1.1test.png

输出结果:

[{--startDate--:--2018-01-03 00:00:00--,--reason--:--a----,--endDate--:--2018-01-24 00:00:00--,--updateDate--:----,--version--:----,--id--:--9999999--,--msgType--:----,--createDate--:--2018-01-03 11:50:44--}]
可以看到,该程序把所有的“"”都替换成了“--”,输入的正则表达式参数明明是“\\"”,经转义之后是“\"”,为什么会匹配所有的“"”呢?

接下来,把正则表达式参数改为“\\\\"”。

1.2test.png

输出结果:

[{"startDate":"2018-01-03 00:00:00","reason":"a--","endDate":"2018-01-24 00:00:00","updateDate":"","version":"","id":"9999999","msgType":"","createDate":"2018-01-03 11:50:44"}]
达到预期的结果。

通过上面两个程序,可以初步判断:java代码中字符串常量层面,将regex参数字面量“\\\\"”转义一遍,其值是“\\"”;在String.replaceAll函数中,对“\\"”进行正则表达式层面的转义得到“\"”。现在,让我们进入到JDK源码中,学习正则表达式层面的具体的转义过程。

String.replaceAll(String regex, String replacement)函数如下:

2.1String.replaceAll.png

它共调用了三个函数,分别是:Pattern.compile(String regex),Pattern.matcher(CharSequence input)和Matcher.replaceAll(String replacement)。


1. 编译(解析)正则表达式,获得Pattern对象

Pattern.compile(String regex)函数如下:

3.1Pattern.compile.png

它返回的是一个Pattern对象。Pattern的构造函数如下:

3.2Pattern - 副本.png
这个构造函数是private级别的,不能被其他类直接调用,只能通过Pattern类的compile(String regex)和compile(String regex, int flags)调用。该构造函数调用了compile(),对regex参数的处理就发生在这个函数里面。


Pattern.compile()函数如下:
3.3Pattern.compile - 副本.png

其中,matchRoot = expr(lastAccept);获得正则表达式匹配根结点。


Pattern.expr(Node end)函数如下:
3.4Pattern.expr - 副本.png
其中,Node node = sequence(end);


Pattern.sequence(Node end)函数如下:
3.5Pattern.sequence - 副本.png

该函数使用了带标签循环“LOOP:”。for ( ; ; )是一个死循环,int ch = peek();取正则表达式当前字符。


Pattern.peek()函数如下:
3.6Pattern.peek.png

其中,temp[0]到temp[3]的值分别为92,92,92,34,即“\\"”转为整型数组。

Pattern.sequence(Node end)函数中,swith开关下,有“case '\':”。读取到第一个“\”后,执行ch = nextEscaped();,获取下一个字符。调用Pattern.unread();将游标cursor自减一,即重新指向第一个“\”,然后执行node = atom();。


Pattern.atom()函数如下:
3.7Pattern.atom - 副本.png

因为游标cursor在Pattern.sequence(Node end)函数中又重新指向了第一个“\”,所以该函数还是会进入“case '\':”。同样地,执行ch = nextEscaped();,获取下一个字符;调用Pattern.unread();将游标cursor自减一,即重新指向第一个“\”。

ch = escape(false, first == 0);,该语句获取到转义后的真实字符,即第二个“\”。


Pattern.escape(boolean inclass, boolean create)函数如下:
3.8Pattern.escape - 副本.png
这个函数就是解析正则表达式的地方。

int ch = skip();,该语句就是获取“\”之后的字符的地方。


Pattern.skip()函数如下:
3.9Pattern.skip.png


Pattern.atom()函数在获取第二个“\”后,执行append(ch, first);将该字符存到Pattern对象的buffer数组中。

Pattern.atom()函数中的for循环,会在读取到temp的0时结束。在Pattern.compile()函数中,可以知道,temp数组长度比正则表达式长度大2,其最后两个元素的值都为0。最终,buffer有两个有效元素,分别是92(“\”)和34(“"”),后续元素全是0。

Pattern.compile(String regex)函数分析到此。


2. 获取匹配器
Pattern.matcher(CharSequence input)函数如下:
4.1Pattern.matcher.png

该Matcher对象关联了Pattern对象和需处理的String对象。


3. 替换字符串

Matcher.replaceAll(String replacement)函数如下:

boolean result = find();,查找第一个能匹配正则表达式的地方并完成替换(若有匹配得上的地方)。
之后,在do...while循环中继续往后查找并替换,直到无匹配得上的地方。把字符串剩余部分接到替换后字符串后面,即可得到想要得到的字符串,即:

5.1Matcher.replaceAll.png

boolean result = find();,查找第一个能匹配正则表达式的地方并完成替换(若有匹配得上的地方)。
之后,在do...while循环中继续往后查找并替换,直到无匹配得上的地方。把字符串剩余部分接到替换后字符串后面,即可得到想要得到的字符串,即:
[{"startDate":"2018-01-03 00:00:00","reason":"a--","endDate":"2018-01-24 00:00:00","updateDate":"","version":"","id":"9999999","msgType":"","createDate":"2018-01-03 11:50:44"}]
欢迎拍砖,谢谢!
展开阅读全文

没有更多推荐了,返回首页