viernes, 17 de mayo de 2013

¿ String Vrs StringBuffer Vrs StringBuilder ?

Esta entrada hace parte de algunas enfocadas a repasos o conceptos de Java, en esta ocasión trabajaremos con las Clases String, StringBuffer y StringBuilder las cuales permiten el trabajo con cadenas de caracteres, a continuación veremos algunos ejemplos básicos con estas Clases y sus métodos principales y mas adelante las compararemos a nivel de rendimiento....


La Clase String.
 
Es común que se confundan los String con tipos de datos primitivos como un int o un double pero eso es incorrecto ya que cuando decimos que trabajamos con una variable String en realidad estamos trabajando con un Objeto y al momento de declararlo lo que hacemos es crear instancias del mismo.

Algo que debemos tener claro es que esta clase esta enfocada a trabajar con cadenas de caracteres constantes entonces cuando definimos un objeto String este será inmutable, es decir que dicho objeto no puede cambiar, si modificamos nuestro String inicial, en realidad lo que hacemos es crear un nuevo objeto de tipo String......
 
Para crear Objetos de la clase String se puede realizar de 2 formas, mediante su constructor o a partir de cadenas constantes o literales:
 
Ej: 

String cadena1 = "CoDejaVu";  o
String cadena2 = new String("CoDejaVu");

cualquiera de las 2 formas anteriores es valida, sin embargo la primera es mas efectiva en cuestiones de rendimiento (aunque esto con un solo objeto no es tan relevante) ya que en la segunda se hace un doble llamado al constructor de la Clase, mientras que en la primera se hace implícito y directo....


Algunos Métodos de la Clase String

charAt(int) : Permite retornar un caracter del String dependiendo del índice enviado como parámetro.
indexOf(String) : Permite retornar el índice desde donde inicia la cadena enviada como parametro.
length() : Retorna el tamaño basado en la cantidad de caracteres que posee la cadena.
replace(Char,Char) : Busca en la cadena inicial donde se encuentre el carácter o cadena enviada en el segundo parámetro y la reemplaza por el primero.
toUpperCase() : Convierte toda la cadena en mayúsculas.
toLowerCase() : Convierte toda la cadena en minúsculas.
trim() : Elimina los espacios en blanco al inicio y final de la cadena.
concat(String) : Concatena el String enviado como parámetro a la cadena original (este metodo tiene su equivalente al usar el operador +)
substring(int) : Elimina la cadena desde la posición 0 hasta la enviada por parámetro -1
substring(int,int) :
Elimina la cadena desde la posición del primer parámetro hasta la posición del segundo.
equalsIgnoreCase(String) : Indica si dos cadenas son iguales, sin tener en cuenta las mayúsculas.

Existen muchos mas métodos de la clase String, sin embargo estos son algunos a mi parecer mas importantes (Necesitaríamos muchas mas entradas para hablar solo del String :S ), ahora veamos como funcionan, para esto creamos el siguiente método...

private void metodosString() {
    String cadena1=" Esto Es Un String ";
  
    /**Imprime la cadena original*/
    System.out.println("Cadena Original = "+cadena1);
    /**retorna la letra s (cuenta tomando el primer caracter como 0 )*/
    System.out.println("Usando charAt(2) = "+cadena1.charAt(2));
    /**retorna la posicion 9 (cuenta tomando el primer caracter como 0 )*/
    System.out.println("Usando indexOf(\"Un\") = "+cadena1.indexOf("Un"));
    /**retorna el tamaño 19 */
    System.out.println("Usando length() = "+cadena1.length());
    /**reemplaza la cadena "un" por la cadena "xxx"*/
    System.out.println("Usando replace(\"Un\", \"xxx\") = "+cadena1.replace("Un", "xxx"));
    /**convierte la cadena en mayusculas*/
    System.out.println("Usando toUpperCase() = "+cadena1.toUpperCase());
    /**convierte en minusculas*/
    System.out.println("Usando toLowerCase() = "+cadena1.toLowerCase());
    /**Elimina los espacios en blanco al principio y final de la cadena*/
    System.out.println("Usando trim() = "+cadena1.trim());
    /**Concatena al String el valor enviado*/
    System.out.println("Usando concat(\" En CoDejaVu\") = "+cadena1.concat("En CoDejaVu"));
    /**elimina la cadena de la posicion 0 a la 5*/
    System.out.println("Usando substring(6) = "+cadena1.substring(6));
    /**elimina la cadena de la posicion 4 a la 14*/
    System.out.println("Usando substring(4, 14) = "+cadena1.substring(4, 14));
    /**indica si dos cadenas son iguales, enviamos la misma cadena, retorna true*/
    System.out.println("Usando equalsIgnoreCase(cadenaOriginal) = "+cadena1.equalsIgnoreCase(cadena1));
    /**indica si dos cadenas son iguales, enviamos otra cadena, retorna false*/
    System.out.println("Usando equalsIgnoreCase(otroString) = "+cadena1.equalsIgnoreCase("Otro String")); 
 }

Al ejecutar el metodoString() obtenemos los siguientes resultados.


Podemos ver las funciones de los métodos mencionados, sin embargo hay que tener en cuenta que en el ejemplo anterior trabajamos siempre con el Objeto String inicial, ya que en ningún momento le asignamos ningún valor ni creamos otro explicitamente, si imprimimos la cadena original nos arroja " Esto Es Un String ".....


Las Clases StringBuffer y StringBuilder.

En estas clases a diferencia de la Clase String las cadenas si se pueden modificar trabajando desde el objeto original, recordemos que si hacemos un cambio en un objeto String en realidad estamos trabajando con un nuevo Objeto (es decir si tenemos cadena=cadena+"hola"; en realidad lo que hacemos es crear un nuevo objeto String apuntando a la misma referencia del Objeto Cadena...) mientras que con StringBuffer o StringBuilder siempre trabajamos desde el mismo Objeto aprovechando el mismo espacio de memoria.

La forma de crear un Objeto de alguna de estas clases es la siguiente.

StringBuffer cadena1=new StringBuffer("CoDejaVu"); y
StringBuilder cadena2=new StringBuilder("CoDejaVu");


Cuando vamos a utilizar muchas cadenas podemos usar estas clases ya que al trabajar con el mismo objeto original hacen mas optimo el proceso, StringBuffer y StringBuilder son prácticamente iguales (posen la misma API), la diferencia es que StringBuilder no es Multihilo, es decir, sus metodos no se encuentran sincronizados haciéndola un poco mas veloz que su equivalente StringBuffer por eso se recomienda que al trabajar con hilos se use esta ultima en vez de StringBuilder....


 Algunos Métodos de StringBuffer y StringBuilder.

appened(String) : Concatena la cadena enviada como parámetro, este método es sobrecargado, quiere decir que acepta cualquier tipo de dato.
delete(int_inicial, int_final) : Elimina la cadena desde la posición inicial hasta la final.
insert(int, String) : Inserta la cadena enviada en la posición definida, este método es sobrecargado, quiere decir que acepta cualquier tipo de dato.
replace(int_inicial, int_final, String) : Reemplaza la cadena enviada en las posiciones de la inicial a la final.
reverse(): imprime la cadena al revés.

Igual que en el ejemplo de la clase String existen muchos mas métodos  pero estos son solo algunos, se recomienda consultar en la API de java sobre estos.......veamos un pequeño ejemplo del funcionamiento.................como lo mencionamos anteriormente dichas clases comparten la misma API por lo cual usan los mismos métodos, por eso solo mostraremos el ejemplo usando StringBuilder....

private void metodosStringBuilder() {
        StringBuilder cadena1=new StringBuilder(" Esto Es Un StringBuilder ");
 /**Imprime la cadena original*/
 System.out.println("Cadena Original = "+cadena1);
 /**concatena la cadena enviada como parametro*/
 System.out.println("Usando append(\"En CoDejaVu\") = "+cadena1.append("En CoDejaVu"));
 /**Elimina la cadena desde la posicion inicial hasta la final*/
 System.out.println("Usando delete(32, 37) = "+cadena1.delete(32, 37));
 /**Inserta la cadena enviada desde la posicion inicial*/
 System.out.println("Usando insert(32, String) = "+cadena1.insert(32, "ejaVu Otra Vez"));
 /**Reemplaza la cadena enviada en las posiciones definidas*/
 System.out.println("Usando replace(2, 4, String) = "+cadena1.replace(2, 4, "xxx"));
 /**Imprime la cadena en reversa*/
 System.out.println("Usando reverse() = "+cadena1.reverse());
    }

Al ejecutar el método anterior obtenemos el siguiente resultado:


a diferencia del ejemplo usando String, aquí todas las operaciones se asignan al objeto inicial, evidenciando que este cada vez va cambiando dependiendo de los métodos que utilicemos.

 
String Vrs StringBuffer Vrs StringBuilder

Con lo anterior podemos decir que las 3 clases sirven para lo mismo, es mas, la Clase String permite un manejo mucho mas simple teniendo en cuenta que podemos omitir crear la instancia de un Objeto simplemente con un " = " que hace ese proceso implícito, o que con el "+" podemos evitar el uso del concat() o con el "+=" podemos realizar la operación y asignarla directamente al objeto String tal como se hace con algunos tipos de datos primitivos entre otras facilidades que nos brinda................ y a decir verdad casi todos nos acostumbramos a usar String por estas y otras razones, sin embargo a nivel de rendimiento son mucho mas efectivas las otras clases que estamos evaluando, ya que como dijimos los String son inmutables por ende dependiendo de lo que realicemos cada vez crearíamos objetos de este tipo y dependiendo también de la cantidad de cadenas que usemos esto seria una carga en tiempo de ejecución, pues se tendría una gran cantidad de objetos en memoria....

A diferencia de este, StringBuffer y StringBuilder siempre trabajarán con el mismo objeto inicial y de esta manera la carga en el heap sera mucho menor.

Como experiencia personal, alguna vez un compañero y yo desarrollamos una aplicación en la cual se Debián realizar muchas operaciones con cadenas las cuales se concatenaban y mostraban en consola, inicialmente usamos obtejos String pero a medida que crecían los datos este proceso tomaba demasiado tiempo hasta que decidimos usar objetos StringBuffer y de esta manera el tiempo se redujo significativamente.

A continuación veremos un ejemplo muy común donde podremos simular lo anterior, en este ejemplo se solicita el ingreso de un texto y se calcula la velocidad de concatenación para cada uno de los 3 casos, el calculo se realiza en ciclos (for) cada uno con una constante de 100000 iteraciones, se evidencia la diferencia de tiempos respectivamente.

/**
 * @author HENAO
 *
 */
public class Cadenas {
 
 /**
  * constante que determina el numero de repeticiones 
  * en cada uno de los tres metodos de concatenacion
  */
 public final long ITERA = 100000;
 /**determina el tiempo total de concatenacion*/
 private long tiempo;
 
 /**
  * @param args
  */
 public static void main(String[] args) {
  
   Cadenas objetoCadenas=new Cadenas();
   String valorCadena="CoDejaVu";
   String usoString=objetoCadenas.calculaTiempoString(valorCadena);
   String usoStringBuffer=objetoCadenas.calculaTiempoStringBuffer(valorCadena);
   String usoStringBuilder=objetoCadenas.calculaTiempoStringBuilder(valorCadena);
  
   System.out.println("Tiempo Concatenacion String : "+usoString+
  "\nTiempo Concatenacion StringBuffer : "+usoStringBuffer+
   "\nTiempo Concatenacion StringBuilder : "+usoStringBuilder);  
 }
 
 
 /**
  * Metodo que calcula el tiempo de concatenacion de la cadena
  * usando la clase String
  * @param valor dato ingresado por el usuario
  * @return tiempo en milisegundos
  */
 public String calculaTiempoString(String valor) 
 {
  String cadena = "";
  tiempo = System.currentTimeMillis();
  for (int i = 0; i < ITERA ; i++) {
   cadena += valor;
  }
  tiempo = System.currentTimeMillis() - tiempo;
  return tiempo + " milisegundos";
 }

 /**
  * Metodo que calcula el tiempo de concatenacion de la cadena
  * usando la clase StringBuffer
  * @param valor dato ingresado por el usuario
  * @return tiempo en milisegundos
  */
 public String calculaTiempoStringBuffer(String valor) 
 {
  StringBuffer cadena = new StringBuffer();
  long tiempo = System.currentTimeMillis();
  for (int i = 0; i < ITERA ; i++) {
   cadena.append(valor);
  }
  tiempo = System.currentTimeMillis() - tiempo;
  return tiempo + " milisegundos";
 }
 /**
  * Metodo que calcula el tiempo de concatenacion de la cadena
  * usando la clase StringBuilder
  * @param valor dato ingresado por el usuario
  * @return tiempo en milisegundos
  */
 public String calculaTiempoStringBuilder(String valor)
 {
  StringBuilder cadena = new StringBuilder();
  tiempo = System.currentTimeMillis();
  for (int i = 0; i < ITERA ; i++) {
   cadena.append(valor);
  }
  tiempo = System.currentTimeMillis() - tiempo;
  return tiempo + " milisegundos";
 }
}

De esta manera obtenemos el siguiente resultado:


y podemos ver la gran diferencia entre los diferentes tipos de objetos, dependiendo de la cantidad de cadenas con las que vamos a trabajar estos resultados varían y en ocasiones el tiempo de concatenación del StringBuffer es igual al del StringBuilder pues como ya mencionamos comparten la misma API, pero siempre se presentará una gran distancia con respecto al String.

Conclusión.

Como conclusión, vimos las diferentes formas que tenemos al momento de trabajar con datos de tipo cadena, se recomienda usar objetos String cuando no tengamos que manipular gran cantidad de cadenas y que estas no vayan a cambiar, usaremos StringBuffer cuando necesitemos trabajar con cadenas cambiantes además de que también trabajemos con varios hilos y StringBuilder cuando no trabajamos con hilos o solo se tiene uno en ejecución.


También te podría Interesar. 


¿Hay algo que quieras anexar o comentar sobre esta entrada?  no dudes en hacerlo.....y si te gustó...... te invito a compartir y Suscribirte ingresando al botón "Participar en este sitio" para darte cuenta de mas entradas como esta ;)

9 comentarios:

  1. Buen post, sencilla explicación ;)

    ResponderEliminar
  2. Hola Cristian.
    Para el calculo de elementos de tipo moneda cual es la mejor recomendación, te coloco un ejemplo, 2000000000 * 15000.
    Que variables me permite realizar mas eficientemente este calculo.?
    Gracias

    ResponderEliminar
    Respuestas
    1. Hola Frank, para trabajar con datos de tipo moneda lo recomendable es usar float o double, dependiendo del caso, tienes que tener en cuenta que float permite 4 Byte (32 bits) y double 8 Bytes (64 bits), por eso double acepta un valor mas grande, solo que te digo que dependiendo del caso porque no se si para tu aplicacion sea muy importante la cuestion de tamaños, ya que double permite un numero mas grande pero ocupa consume mas memoria.............. espero que te sirva ;)

      Gracias por preguntar, un saludo ;)

      Eliminar
  3. Gracias por compartir tus conocimientos, sos muy claro para explicar.

    ResponderEliminar
  4. Hola justo platicaba con un colega sobre este proceso.

    Mi duda es acerca de la legibilidad del código ya que en un Query (que por cierto es un sistema malechote), ocupan el string builder, algo como:

    str.append("SELECT");
    str.append("FROM, blabla");
    str.append("WHERE bla bla");

    Sin embargo el código queda ilegible e instransferible (copiar + pegar) pues hay que aplicar filtros para eliminar los comandos que estan entre todo el Query.

    Ahora.. basándonos en tu ejemplo que sucedería si :

    str.append("SELECT" +
    "FROM, blabla" +
    "WHERE bla bla");

    Intente corriendo concatenando 2 Strings diferentes y al utilizar lo que propongo, solo se tarda un poco más en una iteracion de 10000, (hablando de 1 ms en total).

    Salvo eso, no se que más se le pueda sacar. Saludos desde elporfirio.com

    for (int i = 0; i < ITERA ; i++) {
    cadena.append(valor);
    cadena.append(valor);
    }
    /************************************** */
    for (int i = 0; i < ITERA ; i++) {
    cadena.append(valor + valor);
    }

    ResponderEliminar
  5. Excelente explicacion

    ResponderEliminar
  6. Hola, por favor aclarame esta duda, en la linea:
    String usoString=objetoCadenas.calculaTiempoString(valorCadena);
    Como usas el metodo calculaTiempoString en objetoCadenas si no esta declarado en la clase cadenas?

    ResponderEliminar

Eres libre de realizar cualquier comentario, desde que pueda ayudar con gusto lo atenderé, y si es un critica, bienvenida sea!!!