String vs StringBuilder vs StringBuffer en Java

    Introducci贸n

    Una de las clases m谩s utilizadas en Java es la String clase. Representa una cadena (matriz) de caracteres y, por lo tanto, contiene datos textuales como “隆Hola mundo!”. junto al String clase, hay otras dos clases que se utilizan para prop贸sitos similares, aunque no con tanta frecuencia: StringBuilder y StringBuffer.

    Cada uno existe por su propia raz贸n y, sin conocer los beneficios de las otras clases, muchos programadores novatos solo usan cadenas, lo que lleva a una disminuci贸n del rendimiento y una escasa escalabilidad.

    String

    Inicializar una cadena es tan f谩cil como:

    String string = "Hello World!";
    

    Es at铆pico, como en todos los dem谩s casos, crear铆amos una instancia de un objeto usando el new palabra clave, mientras que aqu铆 tenemos una versi贸n de “acceso directo”.

    Hay varias formas de crear una instancia de Strings:

    // Most common, short way
    String str1 = "Hello World";
    
    // Using the `new` keyword and passing text to the constructor
    String str2 = new String("Hello World");
    
    // Initializing an array of characters and assigning them to a String
    char[] charArray = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
    String str3 = new String(charArray);
    

    Echemos un vistazo al c贸digo fuente de la clase y hagamos algunas observaciones:

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
    
        /**
         * Initializes a newly created {@code String} object so that it represents
         * an empty character sequence.  Note that use of this constructor is
         * unnecessary since Strings are immutable.
         */
        public String() {
            this.value = new char[0];
        }
    
        /**
         * Allocates a new {@code String} so that it represents the sequence of
         * characters currently contained in the character array argument. The
         * contents of the character array are copied; subsequent modification of
         * the character array does not affect the newly created string.
         *
         * @param  value
         *         The initial value of the string
         */
        public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }
        
        ...
    }
    

    Primero podemos observar c贸mo se guarda el texto en s铆, en un char formaci贸n. Dicho esto, es l贸gico que podamos formar una Cadena a partir de una matriz de caracteres.

    Una cosa realmente importante a tener en cuenta aqu铆 es el hecho de que String Se define como final. Esto significa que String es inmutable.

    驴Qu茅 significa esto?

    String str1 = "Hello World!";
    str1.substring(1,4).concat("abc").toLowerCase().trim().replace('a', 'b');
    System.out.println(str1);
    

    Salida:

    Hello World!
    

    Ya que String es final, ninguno de estos m茅todos realmente lo cambi贸. Simplemente devolvieron el estado cambiado que no usamos ni asignamos en ninguna parte. Cada vez que se llama a un m茅todo en una cadena, se crea una nueva cadena, se cambia el estado y se devuelve.

    Nuevamente, echando un vistazo al c贸digo fuente:

    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }
    

    El original str nunca se cambia. Se copia su valor y se le agrega el texto que concatenamos, luego de lo cual un nuevo String es regresado.

    Si hici茅ramos algo como esto:

    String str1 = "Hello World!";
    String str2 = str1.substring(1,4).concat("abc").toLowerCase().trim().replace('a', 'b');
    System.out.println(str2);
    

    Entonces ser铆amos recibidos con la salida:

    ellbbc
    

    Ahora, echemos un vistazo a estas dos cadenas:

    String str1 = "qwerty";
    String str2 = "qwerty";
    

    Cuando instanciamos un String as铆, el valor, en este caso qwerty se guarda en Java Heap Memory, que se utiliza para la asignaci贸n de memoria din谩mica para todos los objetos Java.

    Si bien tenemos dos variables de referencia diferentes en este ejemplo, ambas se refieren a una sola ubicaci贸n de memoria en Java Heap Memory. Si bien puede parecer que hay dos objetos String diferentes, en realidad solo hay uno: str2 nunca se instancia como un objeto, sino que se le asigna el objeto en la memoria que corresponde a str1.

    Esto sucede debido a la forma en que se optimiz贸 Java para Strings. Cada vez que desee crear una instancia de un objeto String como este, el valor que desea agregar a la memoria din谩mica se compara con los valores agregados anteriormente. Si ya existe un valor igual, el objeto no se inicializa y el valor se asigna a la variable de referencia.

    Estos valores se guardan en el llamado Piscina de Strings, que contiene todos los valores de cadena literales. Sin embargo, hay una forma de evitar esto: utilizando el new palabra clave.

    Echemos un vistazo a otro ejemplo:

    String str1 = "qwerty";
    String str2 = "qwerty";
    String str3 = new String("qwerty");
    
    System.out.println(str1 == str2);
    System.out.println(str1 == str3);
    System.out.println(str1.equals(str2));
    System.out.println(str1.equals(str3));
    

    Salida:

    true
    false
    true
    true
    

    Esto es l贸gico, ya que str1 y str2 apuntar al mismo objeto en la memoria. str3 se instancia expl铆citamente como new por lo que se crea un nuevo objeto para 茅l, aunque el literal String ya existe en el grupo. los equals() El m茅todo compara sus valores, no los objetos a los que apuntan, que es la raz贸n por la que devuelve true para todas estas cadenas.

    Es importante notar que substring() y concat() los m茅todos devuelven un nuevo String objeto y gu谩rdelo en el grupo de cadenas.

    Este es un fragmento de c贸digo muy peque帽o, pero si consideramos algunos proyectos grandes que utilizan cientos de String variables y miles de operaciones como substring() o concat(), puede provocar graves p茅rdidas de memoria y retrasos. Eso es exactamente por lo que queremos usar StringBuffer o StringBuilder.

    StringBuffer y StringBuilder

    Mutabilidad

    StringBuffer y StringBuilder los objetos tienen b谩sicamente el mismo valor que un String objeto – una secuencia de caracteres. Ambos StringBuffer y StringBuilder tambi茅n son mutables, lo que significa que una vez que les asignamos un valor, ese valor se procesa como un atributo de un StringBuffer o StringBuilder objeto.

    No importa cu谩ntas veces modifiquemos su valor, como resultado un nuevo String, StringBuffero StringBuilder objeto no lo har茅 ser creado. Este enfoque es mucho m谩s eficiente en el tiempo y consume menos recursos.

    StringBuilder vs StringBuffer

    Estas dos clases son casi id茅nticas entre s铆: utilizan m茅todos con los mismos nombres que devuelven los mismos resultados. Aunque existen dos grandes diferencias entre ellos:

    • Seguridad del hilo: StringBuffer Los m茅todos est谩n sincronizados, lo que significa que solo un subproceso puede llamar a los m茅todos de un StringBuffer instancia a la vez. Por otra parte StringBuilder Los m茅todos no est谩n sincronizados, por lo tanto, varios subprocesos pueden llamar a los m茅todos en StringBuilder clase sin ser bloqueado.

      As铆 que hemos llegado a la conclusi贸n de que StringBuffer es una clase segura para subprocesos mientras StringBuffer no lo es.

      驴Es eso algo de lo que deber铆a preocuparse? Tal vez. Si est谩 trabajando en una aplicaci贸n que utiliza varios subprocesos, puede ser potencialmente peligroso trabajar con StringBuilder.

    • Velocidad: StringBuffer es en realidad dos o tres veces m谩s lento que StringBuilder. La raz贸n detr谩s de esto es StringBuffer sincronizaci贸n: permitir que solo se ejecute 1 subproceso en un objeto a la vez da como resultado una ejecuci贸n de c贸digo mucho m谩s lenta.

    M茅todos

    Ambos StringBuffer y StringBuilder tienen los mismos m茅todos (adem谩s synchronized declaraci贸n de m茅todo en el StringBuilder clase). Repasemos algunos de los m谩s comunes:

    • append()
    • insert()
    • replace()
    • delete()
    • reverse()

    Como puede ver, el nombre de cada m茅todo describe bastante bien lo que hace. Aqu铆 hay una demostraci贸n simple:

    StringBuffer sb1 = new StringBuffer("Buffer no 1");
    System.out.println(sb1);
            
    sb1.append(" - and this is appended!");
    System.out.println(sb1);
    sb1.insert(11, ", this is inserted"); 
    System.out.println(sb1);
    sb1.replace(7, 9, "Number"); 
    System.out.println(sb1);
    sb1.delete(7, 14);
    System.out.println(sb1);
    sb1.reverse();
    System.out.println(sb1);
    

    Salida:

    Buffer no 1
    Buffer no 1 - and this is appended!
    Buffer no 1, this is inserted - and this is appended!
    Buffer Number 1, this is inserted - and this is appended!
    Buffer 1, this is inserted - and this is appended!
    !dedneppa si siht dna - detresni si siht ,1 reffuB
    

    String vs StringBuilder vs StringBuffer

    String StringBuffer StringBuilder

    MudableNosisi
    A salvo de amenazassisiNo
    Tiempo eficienteNoNosi
    Memoria eficienteNosisi

    Nota: Como podemos ver en la tabla anterior, String es menos eficiente en tiempo y memoria, pero eso no significa que nunca debamos volver a usarlo.

    De hecho, String puede ser muy 煤til de usar porque se puede escribir r谩pidamente y si alguna vez desarrolla una aplicaci贸n que almacena cadenas que no se van a manipular / cambiar m谩s adelante, est谩 absolutamente bien usar String.

    Ejemplo de c贸digo

    Para mostrar cu谩n eficiente String, StringBuffery StringBuilder vamos a realizar una prueba de referencia:

    String concatString = "concatString";
    StringBuffer appendBuffer = new StringBuffer("appendBuffer");
    StringBuilder appendBuilder = new StringBuilder("appendBuilder");
    long timerStarted;
    
    timerStarted = System.currentTimeMillis();
    for (int i = 0; i < 50000; i++) {
        concatString += " another string";
    }
    System.out.println("Time needed for 50000 String concatenations: " + (System.currentTimeMillis() - timerStarted) + "ms");
    
    timerStarted = System.currentTimeMillis();
    for (int i = 0; i < 50000; i++) {
        appendBuffer.append(" another string");
    }
    System.out.println("Time needed for 50000 StringBuffer appends: " + (System.currentTimeMillis() - timerStarted) + "ms");
            
    timerStarted = System.currentTimeMillis();
    for (int i = 0; i < 50000; i++) {
        appendBuilder.append(" another string");
    }
    System.out.println("Time needed for 50000 StringBuilder appends: " + (System.currentTimeMillis() - timerStarted) + "ms");
    

    Salida:

    Time needed for 50000 String concatenations: 18108ms
    Time needed for 50000 StringBuffer appends: 7ms
    Time needed for 50000 StringBuilder appends: 3ms
    

    Este resultado puede variar seg煤n su m谩quina virtual Java. Entonces, a partir de esta prueba comparativa, podemos ver que StringBuilder es el m谩s r谩pido en la manipulaci贸n de Strings. El siguiente es StringBuffer, que es entre dos y tres veces m谩s lento que StringBuilder. Y finalmente tenemos String que es, con mucho, el m谩s lento en la manipulaci贸n de Strings.

    Utilizando StringBuilder result贸 en un tiempo ~ 6000 veces m谩s r谩pido que el normal Strings. Lo que se necesitar铆a StringBuilder para concatenar en 1 segundo tomar铆a String 1,6 horas (si pudi茅ramos concatenar tanto).

    Conclusi贸n

    Hemos visto la actuaci贸n de Strings, StringBuffers, y StringBuilders as铆 como sus pros y contras. Ahora, surge la pregunta final:

    驴Cu谩l es el ganador?

    Bueno, la respuesta perfecta a esta pregunta es “Depende”. Lo sabemos Strings son f谩ciles de escribir, f谩ciles de usar y seguros para subprocesos. Por otro lado, son inmutables (lo que significa m谩s consumo de memoria) y muy lentos a la hora de manipular cadenas.

    StringBufferLos s son mutables, eficientes en memoria y seguros para subprocesos. Su ca铆da es la velocidad en comparaci贸n con mucho m谩s r谩pido. StringBuilders.

    Como para StringBuilders, tambi茅n son mutables y eficientes en memoria, son los m谩s r谩pidos en la manipulaci贸n de cadenas, pero desafortunadamente no son seguros para subprocesos.

    Si toma en consideraci贸n estos hechos, 隆siempre tomar谩 la decisi贸n correcta!

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *