Что делает replace, если совпадение не найдено? (под капотом)

У меня очень длинные строки, которые должны удалить шаблон, если он появится. Но это невероятно редкий случай, когда он появляется в строках.

Если я сделаю это:

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

тогда похоже, что я создание новой строки (потому что строки Java неизменяемы), что было бы пустой тратой, если бы исходная строка была в порядке. Должен ли я сначала проверить совпадение, а затем только заменить, если совпадение найдено?

3 ответов


короткий ответ:

проверка документации различных реализаций, похоже, не требует String.replace(CharSequence, CharSequence) метод для возврата той же строки, если совпадение не найдено.

без требования из документации реализация может оптимизировать или не оптимизировать метод в случае, если совпадение не найдено. лучше писать свой код так, как будто нет никакой оптимизации, чтобы убедиться, что он работает правильно на любой реализации или версии среда JRE.

в частности, когда совпадение не найдено, реализация Oracle (версия 8-b123) возвращает тот же строковый объект, в то время как GNU Classpath (версия 0.95) возвращает новый строковый объект независимо.

если вы можете найти любой пункт в любой документации, требующей 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() функция on 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 интерфейс, и так как строка переходит в Matcher давайте взглянем на String.toString:

public String toString() {
    return this;
}

из этого можно сделать вывод, что реализация Oracle возвращает ту же строку, если совпадение не найдено.


Я не нашел окончательного ответа (из документов), но я попробовал это на Jre7 Oracle и обнаружил, что 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 чтобы точно знать, есть ли какие-либо преимущества.


Ok.. В 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) Целевая строка компилируется как литерал узор. и matcher() вызывается на нем путем передачи вызывающей stringInstance к нему.

теперь метод matcher () вернет новый сопоставитель здесь. Просто обратите внимание, что