Introducción
Contenido
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:
Te puede interesar:Implementando Hibernate con Spring Boot y PostgreSQLString 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:
Te puede interesar:Introducción a los algoritmos genéticos en JavaString 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
, StringBuffer
o 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:
Te puede interesar:Expresiones Lambda en Java- Seguridad del hilo:
StringBuffer
Los métodos están sincronizados, lo que significa que solo un subproceso puede llamar a los métodos de unStringBuffer
instancia a la vez. Por otra parteStringBuilder
Los métodos no están sincronizados, por lo tanto, varios subprocesos pueden llamar a los métodos enStringBuilder
clase sin ser bloqueado.Así que hemos llegado a la conclusión de que
StringBuffer
es una clase segura para subprocesos mientrasStringBuffer
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 queStringBuilder
. La razón detrás de esto esStringBuffer
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
Mudable | No | si | si | |
A salvo de amenazas | si | si | No | |
Tiempo eficiente | No | No | si | |
Memoria eficiente | No | si | si |
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
, StringBuffer
y 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 String
s. 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 String
s, StringBuffer
s, y StringBuilder
s 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 String
s 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.
StringBuffer
Los 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. StringBuilder
s.
Como para StringBuilder
s, 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!