StringBuilder против String с учетом замены - PullRequest
32 голосов
/ 11 февраля 2011

При выполнении конкатенации большого количества строк я рекомендовал сделать это, используя StringBuilder как таковое:

StringBuilder someString = new StringBuilder("abc");
someString.append("def");
someString.append("123");
someString.append("moreStuff");

вместо

String someString = "abc";
someString = someString + "def";
someString = someString + "123";
someString = someString + "moreStuff";

, что привело бы ксоздание нескольких строк, а не одной.

Теперь мне нужно сделать нечто подобное, но вместо конкатенации я использую метод String replace как таковой:

String someString = SOME_LARGE_STRING_CONSTANT;
someString = someString.replace("$VARIABLE1", "abc");
someString = someString.replace("$VARIABLE2", "def");
someString = someString.replace("$VARIABLE3", "123");
someString = someString.replace("$VARIABLE4", "moreStuff");

Чтобы выполнить то же самое с помощью StringBuilder, я должен сделать это только для одной замены:

someString.replace(someString.indexOf("$VARIABLE1"), someString.indexOf("$VARIABLE1")+10, "abc");

Так что мой вопрос: «Лучше использовать String.replace и иметь многодополнительных строк, или использовать StringBuilder все еще, и иметь много длинных строк, например, как показано выше? "

Ответы [ 9 ]

44 голосов
/ 11 февраля 2011

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

Просто чтобы отметить,тем не менее, компилятор Java автоматически преобразует пример, подобный этому:

String result = someString + someOtherString + anotherString;

во что-то вроде:

String result = new StringBuilder().append(someString).append(someOtherString).append(anotherString).toString();

Тем не менее, если вы не заменяете большое количество строк, переходите к тому, что будет более читабельным и более понятным. Так что, если вы можете сохранить его более чистым, используя последовательность вызовов 'replace', продолжайте и сделайте это с помощью метода StringBuilder.Разница будет незначительной по сравнению с стрессом, который вы избавите от работы с печальной трагедией микрооптимизаций .

PS

для вашего примера кода (который, как и OscarRyz)указал, что не будет работать, если у вас более одного "$VARIABLE1" в someString, и в этом случае вам нужно будет использовать цикл), вы можете кэшировать результат вызова indexOf в:

someString.replace(someString.indexOf("$VARIABLE1"), someString.indexOf("$VARIABLE1")+10, "abc");

С

int index = someString.indexOf("$VARIABLE1");    
someString.replace(index, index+10, "abc");

Нет необходимости дважды искать строку: -)

7 голосов
/ 11 февраля 2011

Угадай что? Если вы работаете с Java 1.5+, конкатенация работает так же с строковыми литералами

  String h = "hello" + "world";

и

  String i = new StringBuilder().append("hello").append("world").toString();

То же самое.

Итак, компилятор уже сделал всю работу за вас.

Конечно, лучше было бы:

 String j = "hellworld"; // ;) 

Что касается второго, да, это предпочтительнее, но не должно быть таким сложным, с силой «поиска и замены» и небольшим количеством регулярных выражений foo

Например, вы можете определить метод, подобный приведенному в этом примере:

  public static void replace( String target, String replacement, 
                              StringBuilder builder ) { 
    int indexOfTarget = -1;
    while( ( indexOfTarget = builder.indexOf( target ) ) >= 0 ) { 
      builder.replace( indexOfTarget, indexOfTarget + target.length() , replacement );
    }
  }

И ваш код в настоящее время выглядит так:

someString = someString.replace("VARIABLE1", "abc");
someString = someString.replace("VARIABLE2", "xyz");

Все, что вам нужно сделать, это взять в текстовом редакторе триггер что-то вроде этого поиска vi и заменить:

%s/^.*("\(.*\)".\s"\(.*\)");/replace("\1","\2",builder);

Это гласит: "возьмите что-нибудь в круглых скобках, и это похоже на строковый литерал, и поместите это в эту другую строку" .

И ваш код будет выглядеть так:

someString = someString.replace("VARIABLE1", "abc");
someString = someString.replace("VARIABLE2", "xyz");

на это:

replace( "VARIABLE1", "abc", builder );
replace( "VARIABLE2", "xyz", builder );

В кратчайшие сроки.

Вот рабочая демонстрация:

class DoReplace { 
  public static void main( String ... args ) {
    StringBuilder builder = new StringBuilder(
       "LONG CONSTANT WITH VARIABLE1 and  VARIABLE2 and VARIABLE1 and VARIABLE2");
    replace( "VARIABLE1", "abc", builder );
    replace( "VARIABLE2", "xyz", builder );
    System.out.println( builder.toString() );
  }
  public static void replace( String target, String replacement, 
                              StringBuilder builder ) { 
    int indexOfTarget = -1;
    while( ( indexOfTarget = builder.indexOf( target ) ) > 0 ) { 
      builder.replace( indexOfTarget, indexOfTarget + target.length() , 
                       replacement );
    }
  }
}
3 голосов
/ 11 февраля 2011

Я бы сказал, что нужно использовать StringBuilder, но просто написать оболочку, которая облегчит чтение кода и, следовательно, его будет легче поддерживать при сохранении эффективности.= D

import java.lang.StringBuilder;
public class MyStringBuilder
{
    StringBuilder sb;

    public MyStringBuilder() 
    {
       sb = new StringBuilder();
    }

    public void replace(String oldStr, String newStr)
    {
            int start = -1;
            while ((start = sb.indexOf(oldStr)) > -1)
            {
                    int end = start + oldStr.length(); 
                    sb.replace(start, end, newStr);
            }
    }

    public void append(String str)
    {
       sb.append(str);
    }

    public String toString()
    {
          return sb.toString();
    }

    //.... other exposed methods

    public static void main(String[] args)
    {
          MyStringBuilder sb = new MyStringBuilder();
          sb.append("old old olD dudely dowrite == pwn");
          sb.replace("old", "new");
          System.out.println(sb);
    }
}

ВЫХОД:

new new olD dudely dowrite == pwn

Теперь вы можете просто использовать новую версию, которая представляет собой один легкий лайнер

MyStringBuilder mySB = new MyStringBuilder();
mySB.append("old dudley dowrite == pwn");
mySB.replace("old", "new"):
1 голос
/ 11 февраля 2011

Вместо таких длинных строк вы могли бы просто написать метод для замены частей строк StringBuilder, что-то вроде этого:

public StringBuilder replace(StringBuilder someString, String replaceWhat, String replaceWith) {
   return someString.replace(someString.indexOf(replaceWhat), someString.indexOf(replaceWhat)+replaceWhat.length(), replaceWith);
}
0 голосов
/ 23 июня 2017

Хотя микрооптимизация может быть проблематичной, иногда это зависит от контекста, например, если ваша замена выполняется внутри цикла с 10000 итерациями, вы увидите значительное отличие производительности от «бесполезных» оптимизаций .

в большинстве случаев, однако, лучше ошибиться в сторону читабельности

0 голосов
/ 18 декабря 2012

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

private static void replaceAll(StringBuilder builder, String replaceWhat, String replaceWith){
    int occuranceIndex = builder.indexOf(replaceWhat);
    int lastReplace = -1;
    while(occuranceIndex >= 0){
        if(occuranceIndex >= lastReplace){
            builder.replace(occuranceIndex, occuranceIndex+replaceWhat.length(), replaceWith);
            lastReplace = occuranceIndex + replaceWith.length();
            occuranceIndex = builder.indexOf(replaceWhat);
        }else{
            break;
        }
    }
}
0 голосов
/ 22 марта 2012

Все коды парней имеют ошибку .try yourReplace("x","xy"). Это будет loop бесконечно

0 голосов
/ 11 февраля 2011

Если ваша строка действительно велика и вы беспокоитесь о производительности, я бы порекомендовал написать класс, который берет ваш шаблонный текст и список переменных, а затем читает символьную строку источника и строит результат, используя StringBuilder. Это должно быть наиболее эффективным с точки зрения использования процессора и памяти. Кроме того, если вы читаете этот шаблон текста из файла, я не буду загружать все это в память заранее. Обрабатывайте его кусками по мере чтения из файла.

Если вы просто ищете хороший способ построить строку, которая не так эффективна, как StringBuilder, но более эффективна, чем добавление строк снова и снова, вы можете использовать String.format () . Он работает как sprintf () в C. MessageFormat.format () тоже вариант, но он использует StringBuffer.

Здесь есть еще один связанный с этим вопрос: Вставить строку Java в другую строку без объединения?

0 голосов
/ 11 февраля 2011

Может быть класс String для внутреннего использования

indexOf

, чтобы найти индекс старой строки и заменить его новой строкой.

Итакже StringBuilder не является потокобезопасным, поэтому он выполняется намного быстрее.

...