Acabo de encontrar una construcción de consulta sql como esta en mi proyecto:
return (new StringBuilder("select id1, " + " id2 " + " from " + " table")).toString();
¿Consigue este StringBuilder
su objetivo, es decir, reducir el uso de memoria?
Lo dudo, porque en el constructor se utiliza el '+' (operador de concatenar String). ¿Tomará la misma cantidad de memoria que usar String como el código de abajo? s entendí, que difiere al usar StringBuilder.append()
.
return "select id1, " + " id2 " + " from " + " table";
¿Son ambas declaraciones iguales en el uso de la memoria o no? Por favor, aclárelo.
Gracias de antemano.
Editar:
Por cierto, no es mi código. Lo encontré en un proyecto antiguo. Además, la consulta no es tan pequeña como la de mi ejemplo. :)
El objetivo de utilizar StringBuilder, es decir, reducir la memoria. ¿Se ha conseguido?
No, en absoluto. Ese código no está utilizando StringBuilder
correctamente. (Aunque creo que lo has citado mal; seguro que no hay comillas alrededor de id2
y table
).
Tenga en cuenta que el objetivo (normalmente) es reducir la memoria churn en lugar de la memoria total utilizada, para hacer la vida un poco más fácil en el recolector de basura.
¿Recibirá esto la misma cantidad de memoria que el uso de String como se indica a continuación?
No, causará más pérdida de memoria que la concatenación directa que has citado. (Hasta/salvo que el optimizador de la JVM vea que el StringBuilder
explícito en el código es innecesario y lo optimice, si puede).
Si el autor de ese código quiere usar StringBuilder
(hay argumentos a favor, pero también en contra; ver la nota al final de esta respuesta), mejor hacerlo correctamente (aquí asumo que no hay comillas alrededor de id2
y table
):
StringBuilder sb = new StringBuilder(some_appropriate_size);
sb.append("select id1, ");
sb.append(id2);
sb.append(" from ");
sb.append(table);
return sb.toString();
Fíjate que he puesto some_appropriate_size
en el constructor StringBuilder
, para que empiece con capacidad suficiente para todo el contenido que vamos a añadir. El tamaño por defecto utilizado si no se especifica uno es 16 caracteres, que suele ser demasiado pequeño y hace que el StringBuilder
tenga que hacer reasignaciones para hacerse más grande (IIRC, en el JDK de Sun/Oracle, se duplica [o más, si sabe que necesita más para satisfacer un append
específico] cada vez que se queda sin espacio).
Puede que hayas oído que la concatenación de cadenas usará un StringBuilder
bajo cuerda si se compila con el compilador Sun/Oracle. Esto es cierto, utilizará un StringBuilder
para la expresión global. Pero utilizará el constructor por defecto, lo que significa que en la mayoría de los casos, tendrá que hacer una reasignación. Sin embargo, es más fácil de leer. Tenga en cuenta que esto no es cierto para una serie* de concatenaciones. Así, por ejemplo, esto utiliza un StringBuilder
:
return "prefix " + variable1 + " middle " + variable2 + " end";
Esto se traduce aproximadamente en:
StringBuilder tmp = new StringBuilder(); // Using default 16 character size
tmp.append("prefix ");
tmp.append(variable1);
tmp.append(" middle ");
tmp.append(variable2);
tmp.append(" end");
return tmp.toString();
Así que eso'está bien, aunque el constructor por defecto y la reasignación(es) posterior(es) no es ideal, lo más probable es que sea lo suficientemente bueno — y la concatenación es un mucho más legible.
Pero eso'es sólo para una sola expresión. Para ello se utilizan múltiples StringBuilder
:
String s;
s = "prefix ";
s += variable1;
s += " middle ";
s += variable2;
s += " end";
return s;
Eso termina convirtiéndose en algo como esto:
String s;
StringBuilder tmp;
s = "prefix ";
tmp = new StringBuilder();
tmp.append(s);
tmp.append(variable1);
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(" middle ");
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(variable2);
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(" end");
s = tmp.toString();
return s;
...lo cual es bastante feo.
Es importante recordar, sin embargo, que en todos los casos, excepto en unos pocos, no importa y es preferible optar por la legibilidad (que mejora la capacidad de mantenimiento), salvo por un problema específico de rendimiento.
Cuando ya tienes todas las "piezas" que deseas anexar, no tiene sentido utilizar StringBuilder
en absoluto. Usar StringBuilder
y la concatenación de cadenas en la misma llamada como en tu código de ejemplo es aún peor.
Esto sería mejor:
return "select id1, " + " id2 " + " from " + " table";
En este caso, la concatenación de cadenas está ocurriendo en tiempo de compilación de todos modos, por lo que es equivalente al simplificador par:
return "select id1, id2 from table";
El uso de new StringBuilder().append("select id1, ").append(" id2 ")....toString()
en realidad perjudicará el rendimiento en este caso, porque obliga a que la concatenación se realice en tiempo de ejecución, en lugar de en tiempo de compilación. Oops.
Si el código real está construyendo una consulta SQL incluyendo valores en la consulta, entonces ese es otro problema separado, que es que deberías estar usando consultas parametrizadas, especificando los valores en los parámetros en lugar de en el SQL.
Tengo un artículo sobre String
/ StringBuffer
que escribí hace tiempo - antes de que apareciera StringBuilder
. Los principios se aplican a StringBuilder
de la misma manera.
En el código que has publicado no habría ventajas, ya que estás utilizando mal el StringBuilder. Construyes el mismo String en ambos casos. Usando StringBuilder puedes evitar la operación +
en las cadenas usando el método append
.
Deberías usarlo de esta manera:
return new StringBuilder("select id1, ").append(" id2 ").append(" from ").append(" table").toString();
En Java, el tipo String es una secuencia inmutable de caracteres, por lo que al sumar dos Strings la VM crea un nuevo valor String con ambos operandos concatenados.
StringBuilder proporciona una secuencia mutable de caracteres, que se puede utilizar para concatenar diferentes valores o variables sin crear nuevos objetos String, por lo que a veces puede ser más eficiente que trabajar con cadenas
Esto proporciona algunas características útiles, como cambiar el contenido de una secuencia de caracteres pasada como parámetro dentro de otro método, lo que no se puede hacer con Strings.
private void addWhereClause(StringBuilder sql, String column, String value) {
//WARNING: only as an example, never append directly a value to a SQL String, or you'll be exposed to SQL Injection
sql.append(" where ").append(column).append(" = ").append(value);
}
Más información en http://docs.oracle.com/javase/tutorial/java/data/buffers.html