如果没有匹配,替换会怎么做? (引擎盖下)

时间:2014-11-05 06:26:04

标签: java regex string performance replace

我有很长的字符串,如果出现则需要删除模式。但它出现在字符串中是一个非常罕见的边缘情况。

如果我这样做:

str = str.replace("pattern", "");

然后看起来我创建一个新字符串(因为Java字符串是不可变的),如果原始字符串很好,这将是一种浪费。我应该先检查一下匹配项,然后只有找到匹配项才能替换吗?

3 个答案:

答案 0 :(得分:13)

简短回答

检查各种实现的文档,如果找不到匹配项,似乎没有人要求String.replace(CharSequence, CharSequence)方法返回相同的字符串。

如果没有文档要求,在没有找到匹配的情况下,实现可能会也可能不会优化方法。最好编写代码,就像没有优化一样,制作确保它在任何实现或版本的JRE上正确运行。

特别是,当找不到匹配项时,Oracle的实现(版本8-b123)返回相同的String对象,而GNU Classpath(版本0.95)返回一个新的String对象,无论如何。

如果您在找不到匹配项的任何文档中找到要求String.replace(CharSequence, CharSequence)返回相同String对象的文档中的任何条款,请发表评论。

答案很长

  

下面的长答案表明,不同的实现可能会或可能不会优化未找到匹配项的情况。

让我们看一下Oracle的实现和GNU Classpath对String.replace(CharSequence, CharSequence)方法的实现。

GNU Classpath

  

注意:截至撰写本文时,这是正确的。虽然链接将来不太可能发生变化,但链接的内容可能会更改为较新版本的GNU Classpath,并且可能与下面引用的内容不同步。如果更改影响正确性,请发表评论。

让我们看一下GNU Classpath的String.replace(CharSequence, CharSequence)(引用版本0.95)的实现。

public String replace (CharSequence target, CharSequence replacement)
{
    String targetString = target.toString();
    String replaceString = replacement.toString();
    int targetLength = target.length();
    int replaceLength = replacement.length();

    int startPos = this.indexOf(targetString);
    StringBuilder result = new StringBuilder(this);    
    while (startPos != -1)
    {
        // Replace the target with the replacement
        result.replace(startPos, startPos + targetLength, replaceString);

        // Search for a new occurrence of the target
        startPos = result.indexOf(targetString, startPos + replaceLength);
    }
    return result.toString();
}

让我们检查一下StringBuilder.toString()的源代码。由于这决定了返回值,如果StringBuilder.toString()复制了缓冲区,那么我们就不需要进一步检查上面的任何代码。

/**
 * Convert this <code>StringBuilder</code> to a <code>String</code>. The
 * String is composed of the characters currently in this StringBuilder. Note
 * that the result is a copy, and that future modifications to this buffer
 * do not affect the String.
 *
 * @return the characters in this StringBuilder
 */

public String toString()
{
    return new String(this);
}

如果文档没有设法说服您,请按照String构造函数进行操作。最后,调用非公共构造函数String(char[], int, int, boolean)boolean dont_copy设置为false,这意味着新String必须复制缓冲区。

 589:   public String(StringBuilder buffer)
 590:   {
 591:       this(buffer.value, 0, buffer.count);
 592:   }

 245:   public String(char[] data, int offset, int count)
 246:   {
 247:       this(data, offset, count, false);
 248:   }

 594:   /**
 595:    * Special constructor which can share an array when safe to do so.
 596:    *
 597:    * @param data the characters to copy
 598:    * @param offset the location to start from
 599:    * @param count the number of characters to use
 600:    * @param dont_copy true if the array is trusted, and need not be copied
 601:    * @throws NullPointerException if chars is null
 602:    * @throws StringIndexOutOfBoundsException if bounds check fails
 603:    */
 604:   String(char[] data, int offset, int count, boolean dont_copy)
 605:   {
 606:       if (offset < 0)
 607:           throw new StringIndexOutOfBoundsException("offset: " + offset);
 608:       if (count < 0)
 609:           throw new StringIndexOutOfBoundsException("count: " + count);
 610:       // equivalent to: offset + count < 0 || offset + count > data.length
 611:       if (data.length - offset < count)
 612:           throw new StringIndexOutOfBoundsException("offset + count: "
 613:                                                   + (offset + count));
 614:       if (dont_copy)
 615:       {
 616:           value = data;
 617:           this.offset = offset;
 618:       }
 619:       else
 620:       {
 621:           value = new char[count];
 622:           VMSystem.arraycopy(data, offset, value, 0, count);
 623:           this.offset = 0;
 624:       }
 625:       this.count = count;
 626:   }

这些证据表明GNU Classpath的String.replace(CharSequence, CharSequence)实现不会返回相同的字符串。

的Oracle

在Oracle的实现String.replace(CharSequence, CharSequence)(引用版本8-b123)中,该方法使用Pattern类进行替换。

public String replace(CharSequence target, CharSequence replacement) {
    return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
            this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}

Matcher.replaceAll(String)toString()上调用CharSequence函数,并在找不到匹配项时将其返回:

public String replaceAll(String replacement) {
    reset();
    boolean result = find();
    if (result) {
        StringBuffer sb = new StringBuffer();
        do {
            appendReplacement(sb, replacement);
            result = find();
        } while (result);
        appendTail(sb);
        return sb.toString();
    }
    return text.toString();
}

String实现CharSequence接口,由于String将自身传递到Matcher,让我们看一下String.toString

public String toString() {
    return this;
}

由此可以得出结论,当找不到匹配项时,Oracle的实现会返回相同的String。

答案 1 :(得分:2)

我没有找到明确的答案(来自文档),但我在Oracle的JRE7上尝试了这一点,发现replace将引用返回到相同的字符串

以下是我用于测试的代码:

public class NoReplace {
    public static void main(String[]args) {
        String a = "hello";

        /* Test: replacement with no match */
        String b = a.replace("X", "H");
        /* a and b are still the same string? */
        System.out.println(b == a); // true

        /* Sanity: replacement WITH a match */
        String c = a.replace("h", "H");
        /* a and c are still the same string? */
        System.out.println(c == a); // false
    }
}

但我有兴趣看到replace vs contains的一些基准,以确定是否有任何优势。

答案 2 :(得分:2)

好的..在Java 8中。当你拨打myString.replace()时会发生这种情况。

public String replace(CharSequence target, CharSequence replacement) {
    return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
            this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}

Pattern.compile(target.toString(), Pattern.LITERAL).matcher( this) 目标String编译为 Literal 模式。通过将调用stringInstance传递给它来调用matcher()

现在matcher()方法将返回一个新的匹配器。请注意,text的{​​{1}}字段将是当前对象(matcher),即调用this的String对象。

接下来,在replace()中,我们有以下代码:        replaceAll()

即,

boolean result = find();