String, StringBuilder y StringBuffer in Java

String objects represent strings or sequences of characters. This object class is special in Java.

We can see that it is a special class because we can define literal objects and because we can call methods on these literals:

"hello world".toUpperCase()

 

"hello world" is a literal object, on which we call its method toUpperCase().

Another feature, the most important one, is that String objects are immutable, which can bring big performance problems into our programs.

 

String objects are constants and immutable

Once the String objects are created, they can not be modified. To understand this, let's look at the following code:

String s = "Hello";
s = s + "edu"; 
 

In the first line, a String "Hello" object is created and a reference to it, is stored in the s variable.

In the second line another String "edu" object is created and after that we have the concatenation of the objects; "Hello" and "edu". Concatenation produces a new String "Hello edu" object whose reference will be stored in the variable s.

The "Hello" and "edu" objects are obsolete because nobody uses their object reference; so they are not used. The Java virtual machine has a "garbage collector", which cleans these object from memory.

Memory problems with Strings

When we manipulate Strings; concatenating, inserting or replacing characters, many objects are created which are quickly discarded. This object creation may cause an increase in memory usage.

The Java garbage collector cleans memory, but this has a time cost. If we create and destroy many objects, the performance of our program may decrease substantially.

Solution to the efficiency problems of String with StringBuilder and StringBuffer

StringBuffer and StringBuilder are classes that allow you to create objects that store character strings that can be modified without creating new objects.

StringBuilder and StringBuffer have append, replace and insert methods, which allow you to manipulate strings.

StringBuilder and StringBuffer are almost equal. StringBuffer can be used in concurrent programs, which is known as "thread safe". This makes StringBuilder more efficient in no concurrent programs.

Performance example of String versus StringBuilder

In the following example, a chain of 100,000 letters x is created in two different methods. One method uses the String class and the other method uses the StringBuilder class. We measure the duration of the two methods and print them on screen. At the end, we compare the two chains created to ensure that the two methods returned the same.

package com.edu4java.javatutorials;

public class StringBuilderExample {

	public static void main(String[] args) {

		System.out.println("start genWithString");
		long timeStart = System.currentTimeMillis();
		String s1 = genWithString();
		long timeSpend = (System.currentTimeMillis() - timeStart);
		System.out.println("end genWithString. Time:" + timeSpend+" Milliseconds");

		System.out.println("start genWithStringBuilder");
		timeStart = System.currentTimeMillis();
		String s2 = genWithStringBuilder();
		timeSpend = (System.currentTimeMillis() - timeStart);
		System.out.println("end genWithStringBuilder. Time:" + timeSpend+" Milliseconds");
		
		System.out.println("s1.equals(s2)="+s1.equals(s2));
	}

	private static String genWithStringBuilder() {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < 100000; i++) {
			sb.append("x");
		}
		return sb.toString();
	}

	private static String genWithString() {
		String s = "";
		for (int i = 0; i < 100000; i++) {
			s = s + "x";
		}
		return s;
	}
}

After running this program on my machine the console output is:

start genWithString
end genWithString. Time:3050 Milliseconds
start genWithString
end genWithString. Time:4 Milliseconds
s1.equals(s2)=true
  

The genWithString () method took 3,050 milliseconds while genWithStringBuilder () took 4 milliseconds. This will depend on the speed of your computer. You can modify the 100,000 by 1,000,000 or more to see how serious the problem can be.