Java中字符串替换函数replace与replaceAll的区别

本文已同步发布在微信公众号:灰灰的Rom笔记
微信扫码获取更多内容:
灰灰的Rom笔记


背景

业余时间又爬了下网站,这回碰到点问题,爬下来的数据有点奇葩。说是 xml 格式吧,又有些奇怪的东西,而且时不时来个\n,有效数据还被<![CDATA[]]> 包着在。这里我截取了部分贴出来:(为了方便查看,做了简单的格式化)

dwr.engine._remoteHandleCallback('0','0',"
<?xml version=\"1.0\" encoding=\"utf-8\"?>\n
<title>
    <![CDATA[\u5173\u4E8E\u8F6C\u53D1\u201C\u6E56\u5317\u5DE5\u5EFA\u676F\u201D\u7B2C\u516D\u5C4A\u6E56\u5317\u7701\u5927\u5B66\u751F\u7ED3\u6784\u8BBE\u8BA1\u7ADE\u8D5B\u5F81\u96C6\u5FBD\u6807\u7684\u901A\u77E5]]>
</title>\n
<link>
    <![CDATA[/news/152473431173875997.html]]>
</link>\n
<category>
    <![CDATA[\u6559\u52A1\u5904]]>
</category>\n
<pubDate>2018-04-26 17:10:35</pubDate>\n
<xwbh>152473431173875997</xwbh>\n
<color>null</color>\n
<spanpic>pic</spanpic>\n

如何解析这个数据并不是难事,Jsoup轻松搞定,至于如何使用Jsoup,不是本文重点,这里不做介绍。

Jsoup解析数据时,只能解析到标签,比如上面的 title,通过Jsoup解析出来就是:

<![CDATA[\u5173\u4E8E\u8F6C\u53D1\u201C\u6E56\u5317\u5DE5\u5EFA\u676F\u201D\u7B2C\u516D\u5C4A\u6E56\u5317\u7701\u5927\u5B66\u751F\u7ED3\u6784\u8BBE\u8BA1\u7ADE\u8D5B\u5F81\u96C6\u5FBD\u6807\u7684\u901A\u77E5]]>

前面已经说过了,有效数据是<![CDATA[]]> 中间夹着的部分,如果就这么几个字串,那通过字符串截取方法也能轻松拿到有效数据。但是上面贴出来的只是局部,实际整个数据是一个list,里面有很多的<![CDATA[]]>,如果一个个去截取字串,无疑会很繁琐。
因此我的做法是:先将整个数据进行字符串替换,将<![CDATA[]]> 替换为空串"";然后交给Jsoup去解析,这样就可以直接一个循环一步到位了。

字符串替换

基于上面的思路,如何一次性替换掉所有的<![CDATA[]]> 就成了关键。好在 String 类就有替换方法:replacereplaceAll,我们直接拿过来用就可以了。

但是我这里就被这两个方法的名字迷惑了,想着是替换全部的<![CDATA[]]> 嘛,那replaceAll在合适不过了。于是就这么用了:

//这里先将上面那些奇怪的编码转换一下
html = AsciiUtils.ascii2Native(html);
//然后替换所有字串
html = html.replaceAll("<![CDATA[", "").replaceAll("]]>", "");

结果这么一写出来,AS 就在提示参数<![CDATA[有问题:Unclosed character class
这下懵了,然后老老实实看了下 replaceAll 的说明:

Replaces each substring of this string that matches the given regular expression with the given replacement.

replaceAll 实现方式:

public String replaceAll(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

意思很明显,第一个参数 regex 是正则表达式,这个方法的作用就是用 replacement 去替换所有满足 regex的内容。而[也是正则表达式的关键字符,因此这里没有经过处理直接使用就出现了参数问题。

这里我对正则表达式了解的不多,也就没有深究 replaceAll 方法了,转而看了下 replace 的说明和实现:

/**
  * Replaces each substring of this string that matches the literal target
  * sequence with the specified literal replacement sequence. The
  * replacement proceeds from the beginning of the string to the end, for
  * example, replacing "aa" with "b" in the string "aaa" will result in
  * "ba" rather than "ab".
  */
public String replace(CharSequence target, CharSequence replacement) {
    if (target == null) {
        throw new NullPointerException("target == null");
    }

    if (replacement == null) {
        throw new NullPointerException("replacement == null");
    }

    String replacementStr = replacement.toString();
    String targetStr = target.toString();

    // Special case when target == "". This is a pretty nonsensical transformation and nobody
    // should be hitting this.
    //
    // See commit 870b23b3febc85 and http://code.google.com/p/android/issues/detail?id=8807
    // An empty target is inserted at the start of the string, the end of the string and
    // between all characters.
    final int len = length();
    if (targetStr.isEmpty()) {
        // Note that overallocates by |replacement.size()| if |this| is the empty string, but
        // that should be a rare case within an already nonsensical case.
        StringBuilder sb = new StringBuilder(replacementStr.length() * (len + 2) + len);
        sb.append(replacementStr);
        for (int i = 0; i < len; ++i) {
            sb.append(charAt(i));
            sb.append(replacementStr);
        }

        return sb.toString();
    }

    // This is the "regular" case.
    int lastMatch = 0;
    StringBuilder sb = null;
    for (;;) {
        int currentMatch = indexOf(this, targetStr, lastMatch);
        if (currentMatch == -1) {
            break;
        }

        if (sb == null) {
            sb = new StringBuilder(len);
        }

        sb.append(this, lastMatch, currentMatch);
        sb.append(replacementStr);
        lastMatch = currentMatch + targetStr.length();
    }

    if (sb != null) {
        sb.append(this, lastMatch, len);
        return sb.toString();
    } else {
        return this;
    }
}

这里意思就是说,替换每一个满足条件的字串。而且替换过程是从字符串开始到结束单向的,不会出现倒退;比如说:在aaa里面用b替换aa,结果是ba,而不是ab。用也就是说 replace 和 replaceAll 不是字面意思,它俩都是替换所有满足条件的内容
另外,这里replace的实现方式也非常严谨,对target == ""的情况也进行了处理,处理方式是在原字串首尾和其所有字符之间都添加 replacement。举个栗子:

String str = "123";
str.replace("", "X");

结果是:X1X2X3X

小结

1、replace 和 replaceAll 都是替换所有满足条件的内容,只不过replace的匹配条件是普通的字串,而replaceAll的匹配条件是正则表达式。
2、当要被处理的字符串比较庞大时,replaceAll 的效率总是比 replace 的快一些;当字符串量不大时,二者效率不相上下。
3、当字符串无法确定是否具有转义字符时,而且也不需要转义时,建议使用 replace 函数;否则,使用 replaceAll 函数。

猜你喜欢

转载自blog.csdn.net/shawnxiafei/article/details/80253094
今日推荐