String, StringBuilder y StringBuffer en Java

Los objetos String representan cadenas o secuencias de caracteres. Esta clase de objetos es especial en Java.

Un ejemplo de que esta es una clase especial es que se pueden definir objetos literales y podemos llamar a métodos sobre los mismos literales.

"hello world".toUpperCase()

 

"hello world" es un objeto literal, sobre el cual llamamos a su método toUpperCase().

Otra particularidad, la más importante de entender, es que los objetos String son inmutables, lo que puede traer grandes problemas de performance en nuestros programas.

 

Los objetos String son constantes e inmutables

Una vez creados, los objetos Strings no pueden ser modificados. Para entender esto analicemos el siguiente código:

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

En la primer línea, se crea un objeto String “Hello” y se guarda una referencia a él en la variable s.

En la segunda línea se crea otro objeto String “edu” y se realiza la operación concatenación de los objetos “Hello “ y “edu”. La concatenación produce un nuevo objeto String “Hello edu” cuya referencia será guardada en la variable s.

Los objetos “Hello” y “edu” son obsoletos ya que nadie les hace referencia, por lo que nadie los usará. La máquina virtual de Java tiene un recolector de basura; “garbage collector”, que se encarga de limpiar de la memoria estos objetos.

Problemas de memoria con Strings

Cuando manipulamos Strings, concatenando, insertando o reemplazando caracteres se crean muchos objetos que son descartados rápidamente. Esta creación de objetos puede producir un incremento en el uso de la memoria.

El recolector de basura de Java, limpia la memoria pero esto tiene un costo de tiempo. Si creamos y destruimos muchos objetos, la performance de nuestro programa puede decaer sustancialmente.

Solución a los problemas de eficiencia de String con StringBuilder y StringBuffer

StringBuilder y StringBuffer son clases que permiten crear objetos que almacenan cadenas de caracteres que pueden ser modificadas sin necesidad de crear nuevos objetos.

Los métodos append, replace e insert que poseen StringBuilder y StringBuffer, permiten manipular las cadenas de caracteres.

StringBuilder y StringBuffer son casi iguales. StringBuffer puede ser usado en programas concurrentes, lo que se conoce como “thread safe”. Esto hace que StringBuilder sea más eficiente en programas no concurrentes.

Ejemplo de performance String versus StringBuilder

En el siguiente ejemplo, se crea una cadena de 100.000 letras x en dos métodos distintos. Uno usa la clase String y otro la clase StringBuilder. Se miden los tiempos de los dos métodos y se imprimen por pantalla. Al final se comparan las dos cadenas producidas para asegurarnos que los dos métodos produjeron lo mismo.

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

Después de ejecutar este programa en mi máquina la salida por la consola es:

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

El método genWithString() tomó 3.050 milisegundos mientras que el genWithStringBuilder() tomo 4 milisegundos. Esto dependerá de la velocidad de vuestra computadora. Pueden modificar los 100,000 de las sentencias for por 1.000.000 o más para ver que tan grave puede llegar a ser este problema.