35

When doing concatenating lots of strings, I have been recommended to do it using a StringBuilder as such:

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

as opposed to

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

which would result in the creation of quite a few Strings, as opposed to one.

Now, I need to do a similar thing, but instead of using concatenation I use the replace method of String as such:

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");

To accomplish the same thing using StringBuilder, I have to do this, just for one replace:

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

So my question is: "Is it better to use String.replace and have lots of extra Strings created, or to use StringBuilder still, and have lots of long winded lines such as the one above?"

DanielGibbs
  • 9,910
  • 11
  • 76
  • 121
  • If is generating perfomance problems, change it. If there are no more important changes to perform, change it. If the input is really really large and used very often, change it. BTW, the secont approach, won't work since it will only replace it once, you have to put it on a while loop. Take a look at my answer. – OscarRyz Feb 11 '11 at 04:46

9 Answers9

47

It is true that StringBuilder tends to be better than concatenating or modifying Strings manually, since StringBuilder is mutable, while String is immutable and you need to create a new String for each modification.

Just to note, though, the Java compiler will automatically convert an example like this:

String result = someString + someOtherString + anotherString;

into something like:

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

That said, unless you're replacing a whole lot of Strings, go for whichever is more readable and more maintainable. So if you can keep it cleaner by having a sequence of 'replace' calls, go ahead and do that over the StringBuilder method. The difference will be negligible compared to the stress you save from dealing with the sad tragedy of micro-optimizations.

PS

For your code sample (which, as OscarRyz pointed out, won't work if you have more than one "$VARIABLE1" in someString, in which case you'll need to use a loop), you could cache the result of the indexOf call in:

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

With

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

No need to search the String twice :-)

Zach L
  • 16,072
  • 4
  • 38
  • 39
  • This fails if you have two $VARIABLE1 in the input ( it only replaces the first. You have to put that on a while loop. – OscarRyz Feb 11 '11 at 04:45
  • @OscarRyz D'oh! True. Didn't even notice that :-p. The main reason I wrote that code sample was just to point out to the questioner that they shouldn't have to call indexOf twice. – Zach L Feb 11 '11 at 04:48
  • Which is a good point btw. I was trying the same in a sample and realize until then. It's easy to forget. See my ans – OscarRyz Feb 11 '11 at 04:51
  • @OscarRyz @ZachL : Example: `builder = new StringBuilder(120);builder.append(a()).append(b()).append(c());` Hi, If we know the final size, say 120 that the output string is going to be, wont the above code better than the code auto-generated in compilation process. Note, that methods a,b,c are not returning static hard coded strings, so compiler cannot know about what value to initialize builder with. – saurabheights Nov 24 '16 at 03:53
8

Guess what? If you are running with Java 1.5+ the concatenation works the same with string literals

  String h = "hello" + "world";

and

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

Are the same.

So, the compiler did the work for you already.

Of course better would be:

 String j = "hellworld"; // ;) 

As for the second, yeap, that's preferred, but should't be that hard, with the power of "search and replace" and a bit of regex foo

For instance you can define a method like the one in this sample:

  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 );
    }
  }

And your code currently looks like this:

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

All you have to do is grab text editor an trigger something like this vi search and replace:

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

That read: "take anything in parenthesis and that looks like a string literal, and put it in this other string".

And your code will look from this:

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

to this:

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

In no time.

Here's a working demo:

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 );
    }
  }
}
OscarRyz
  • 196,001
  • 113
  • 385
  • 569
  • p.s what happens if the instance of the target starts at position 0 ??? Your while loop exiting condition indicates that it would not replace it. – Matthew Cox Feb 11 '11 at 06:11
  • 2
    Actually, in your very first example, the compiler in fact does not generate the StringBuilder-solution, but your third code line, since it is a compile time constant. So these are equivalent. – Paŭlo Ebermann Feb 11 '11 at 10:39
3

I would say go for using StringBuilder but simply write a wrapper that facilitates making the code more readable and thus more maintainable, while still maintaining efficiency. =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);
    }
}

OUTPUT:

new new olD dudely dowrite == pwn

Now you can just use the new version that is one easy liner

MyStringBuilder mySB = new MyStringBuilder();
mySB.append("old dudley dowrite == pwn");
mySB.replace("old", "new"):
Matthew Cox
  • 13,566
  • 9
  • 54
  • 72
  • Fails with input `"old old dudley"` – OscarRyz Feb 11 '11 at 05:47
  • @OscarRyz Interesting. I have it in a project and the output is exactly what is expected. Bare in mind I didn't add the append(String) or String toString() methods. I decided to add them for clarity – Matthew Cox Feb 11 '11 at 05:51
  • Probably you never have more than 1 string to replace. Try with `"old old dudley"` and you'll get `"new old dudley"` – OscarRyz Feb 11 '11 at 05:59
  • @OscarRyz Haha my apologies. I forgot that it needs to replace all instances of the old string. Correction made. – Matthew Cox Feb 11 '11 at 06:09
1

Instead of having long lines like that, you could just write a method for replacing parts of StringBuilder strings, something along the lines of this:

public StringBuilder replace(StringBuilder someString, String replaceWhat, String replaceWith) {
   return someString.replace(someString.indexOf(replaceWhat), someString.indexOf(replaceWhat)+replaceWhat.length(), replaceWith);
}
Val Schuman
  • 1,798
  • 2
  • 18
  • 23
  • I was thinking about the same, but this needs a while loop to replace them all, and not only the first occurrence. – OscarRyz Feb 11 '11 at 04:51
0

Jam Hong is correct - the above solutions all contain the potential to loop infinitely. I guess the lesson to take away here is that micro optimisations can often cause all sorts of horrible issues and don't really save you much. Still, be that as it may - here is a solution that will not infinite loop.

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;
        }
    }
}
ChrisR
  • 169
  • 1
  • 3
  • This acts like replaceFirst for StringBuilder builder = new StringBuilder("Var x and x and x and x"); replaceAll(builder, "x", "xy"); – irmakoz Mar 19 '13 at 14:05
0

while it's true that micro optimizations can be problematic, it sometimes depends on the context, for instance, if your replace happens to run inside of a loop with 10000 iterations, your will see a significant performance difference from the "useless" optimizations.

in most cases however, it's best to err on the side of readability

Austin_Anderson
  • 900
  • 6
  • 16
0

May be the String Class internally uses

indexOf

method to find index of old string and replace it with new string.

And also StringBuilder is not threadsafe so it executes much faster.

Ankit
  • 2,753
  • 1
  • 19
  • 26
0

If your string really is large and you're worried about performance I would recommend writing a class which takes your template text and a list of variables, then reads over the source string character by character and builds the result using StringBuilder. That should be the most efficient both in terms of CPU and memory usage. Also, if you are reading this template text from a file I wouldn't load it all into memory up front. Process it in chunks as you read it from the file.

If you're just looking for a nice way to build a string that's not quite as efficient as StringBuilder but more efficient than appending strings over and over you can use String.format(). It works like sprintf() in C. MessageFormat.format() is an option too but it uses StringBuffer.

There is another related question here: Inserting a Java string in another string without concatenation?

Community
  • 1
  • 1
Sarel Botha
  • 12,419
  • 7
  • 54
  • 59
0

All guys' codes have a bug .try yourReplace("x","xy").It will loop infinitely

Jam Hong
  • 109
  • 1
  • 2
  • 4