如果找不到匹配项,replace 会做啥? (引擎盖下)

Posted

技术标签:

【中文标题】如果找不到匹配项,replace 会做啥? (引擎盖下)【英文标题】:What does replace do if no match is found? (under the hood)如果找不到匹配项,replace 会做什么? (引擎盖下) 【发布时间】:2015-01-01 06:38:39 【问题描述】:

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

如果我这样做:

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

然后看起来我正在创建一个新字符串(因为 Java 字符串是不可变的),如果原始字符串没问题,这将是一种浪费。我是否应该先检查匹配项,然后仅在找到匹配项时替换?

【问题讨论】:

What does replace do if no match is found? Ans;它按原样打印原始字符串。 @AvinashRaj 是的。你读到最后了吗? 我发现String.java 的一个版本似乎通过正则表达式模式匹配器来实现replace(),即使没有涉及正则表达式。看起来这可能会使事情变慢一些,但是您必须对其进行基准测试才能确定,并且答案可能因 JVM 而异。在任何情况下,都不要指望replace 的结果是==!= 到原始字符串,因为javadocs 没有指定。 【参考方案1】:

简答

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

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

特别是,当没有找到匹配项时,Oracle 的实现(版本 8-b123)返回相同的字符串对象,而 GNU 类路径(版本 0.95)无论如何都会返回一个新的字符串对象。

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

长答案

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

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

GNU 类路径

注意:截至撰写本文时,这是正确的。虽然该链接将来不太可能更改,但该链接的内容可能会更改为较新版本的 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的实现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)CharSequence 上调用toString() 函数并在找不到匹配项时返回:

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 的实现在找不到匹配项时返回相同的字符串。

【讨论】:

有趣的观察。 +1 :)【参考方案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) 目标字符串被编译为 Literal 模式。并通过将调用 stringInstance 传递给它来调用 matcher()

现在 matcher() 方法将在这里返回一个新的匹配器。请注意matchertext 字段将是当前对象(this),即调用replace() 的字符串对象。

接下来,在replaceAll() 中,我们有以下代码: boolean result = find();

即,

public String replaceAll(String replacement) 
    reset();
    boolean result = find();  --> returns false. 
    if (result) 
        StringBuffer sb = new StringBuffer();
        do 
            appendReplacement(sb, replacement);
            result = find();
         while (result);
        appendTail(sb);
        return sb.toString();
    
    return text.toString(); --> same String

   if `find()` returns false, then ,matcher.text is returned which is the original String

【讨论】:

酷。你有来自 Java 规范的参考吗? @ColBeseder - here 是来自 Oracle 文档的参考。 这是对 replace(char,char) 的引用。这不是问题中使用的方法。在这种情况下,replace(CharSequence, CharSequence) 的 javaDoc 没有指定所需的行为。 Eran 是对的——您的 #1 中的行为无法保证,并且可能会随着未来的 JRE 而改变。 答案已更新。最终结果是相同的。即,将返回相同的字符串。【参考方案3】:

我还没有找到明确的答案(来自文档),但我在 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
    

但我有兴趣查看 replacecontains 的一些基准,以确定是否有任何优势。

【讨论】:

以上是关于如果找不到匹配项,replace 会做啥? (引擎盖下)的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 regex char 可选时找不到匹配项

如果在 Hive SQL 中找不到匹配项,则提供要加入的备用列

C++ - 如果构造函数是私有的,这会做啥?

如果没有顺序的内存空间,realloc 会做啥?

如果构造函数中发生错误,Qt 会做啥? [复制]

如果我正在读取的字节还不存在,BinaryReader 会做啥?