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 *