2012-01-04 47 views
79

acabo de encontrar alguna consulta SQL construir como esto en mi proyecto:forma correcta de utilizar StringBuilder

return (new StringBuilder("select id1, " + " id2 " + " from " + " table")).toString(); 

¿Este StringBuilder lograr su objetivo, es decir la reducción de uso de la memoria?

Lo dudo, porque en el constructor se usa el operador '+' (String concat). ¿Tomará la misma cantidad de memoria que usar String como el código de abajo? s Lo entendí, es diferente cuando se usa StringBuilder.append().

return "select id1, " + " id2 " + " from " + " table"; 

¿Ambas declaraciones son iguales en uso de memoria o no? Por favor aclara

¡Gracias de antemano!

Editar:

Por cierto, no es mi código. Lo encontré en un viejo proyecto. Además, la consulta no es tan pequeña como la de mi ejemplo. :)

Respuesta

167

El objetivo de usar StringBuilder, es decir, reducir la memoria. ¿Se logra?

No, en absoluto. Ese código no está utilizando StringBuilder correctamente. (Creo que has citado en forma inexacta, sin embargo, ciertamente no hay comillas id2 y table?)

Tenga en cuenta que el objetivo (por lo general) es reducir la memoria rotación en lugar de la memoria total utilizada, para hacer la vida un poco más fácil en el recolector de basura.

¿Se tardará la memoria en utilizar String como se muestra a continuación?

No, le causa la rotación más memoria de la que sólo la recta concat usted ha citado. (Hasta/a menos que el optimizador JVM ve que la explícita StringBuilder en el código es innecesario y optimiza a cabo, si se puede.)

Si el autor de ese código quiere usar StringBuilder (hay argumentos a favor, sino también contra , ver nota al final de esta respuesta), mejor hacerlo correctamente (aquí estoy asumiendo que no son en realidad comillas 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(); 

Tenga en cuenta que he enumerado en el some_appropriate_sizeStringBuilder constructor, por lo que comienza con suficiente capacidad para todo el contenido que vamos a agregar. El tamaño predeterminado utilizado si no especifica uno es 16 characters, que suele ser demasiado pequeño y hace que el StringBuilder tenga que hacer reasignaciones para hacerse más grande (IIRC, en el JDK Sun/Oracle, se dobla a sí mismo [o más, si sabe que necesita más para satisfacer un append específico] cada vez que se queda sin espacio).

Usted puede haber oído que la concatenación de cadenas se utilice un StringBuilder bajo las sábanas si se compila con el compilador de Sun/Oracle. Esto es cierto, usará un StringBuilder para la expresión general.Pero usará el constructor predeterminado, 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 es no verdadero de un serie de concatenaciones. Así, por ejemplo, este utiliza una StringBuilder:

return "prefix " + variable1 + " middle " + variable2 + " end"; 

Se podría traducir como:

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(); 

por lo que está bien, aunque el constructor por defecto y la posterior reasignación (s) no es lo ideal, lo más probable es es lo suficientemente bueno   — y la concatenación es un lote más legible.

Pero eso es solo para una sola expresión. Múltiples StringBuilder s se utilizan para esto:

String s; 
s = "prefix "; 
s += variable1; 
s += " middle "; 
s += variable2; 
s += " end"; 
return s; 

que 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; 

... que es bastante feo.

Es importante recordar, sin embargo, que en muy pocos casos no importa y se prefiere la legibilidad (que mejora la capacidad de mantenimiento) salvo un problema de rendimiento específico.

+2

Explique por qué no se logra eso para ayudarlo a comprender eso. :) – Thomas

+1

No crea una instancia separada de 'StringBuilder' para cada concatenación; crea una para toda la expresión. Entonces '" select id1, "+ id2 +" de "+ table" solo crearía un solo 'StringBuilder', y sería significativamente más fácil de leer la IMO. Pruébalo y descompila el código para ver ... –

+0

@JonSkeet: Gracias, estaba pensando en el caso de la expresión múltiple, no en el caso de la expresión única. Fijo. Apreciar la nota. –

2

En el código que ha publicado no habría ventajas, ya que está haciendo un uso indebido de StringBuilder. Usted construye el mismo String en ambos casos. Usando StringBuilder puedes evitar la operación + en Strings usando el método append. Debe usarlo de esta manera:

return new StringBuilder("select id1, ").append(" id2 ").append(" from ").append(" table").toString(); 

En Java, el tipo de cuerdas es una secuencia de caracteres inmutable, por lo que cuando se agregan dos cadenas de la máquina virtual crea un nuevo valor de cadena con las dos operandos concatenados.

StringBuilder proporciona una secuencia mutable de los personajes, que se pueden utilizar para concat diferentes valores o variables sin crear nuevos objetos String, y por lo que a veces puede ser más eficiente que la utilización de cadenas

Esto proporciona algunas características útiles, como cambiar el contenido de una secuencia de char pasó como parámetro dentro de otro método, lo cual 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

+1

No, no deberías. Es menos legible que usar '+', que se convertirá en el mismo código de todos modos. 'StringBuilder' es útil cuando no se puede realizar toda la concatenación en una sola expresión, pero no en este caso. –

+1

Entiendo que la cadena en la pregunta se publica como un ejemplo. No tendría sentido construir una cadena "fija" como esta ni con StringBuilder ni agregar diferentes fragmentos, ya que solo se podría definir en una sola constante "select id1, id2 from table" –

+0

Pero incluso si hubiera valores no constantes de las variables, aún usaría un solo 'StringBuilder' si usa' return 'select id1,' '+ foo +' 'algo más' '+ bar; '' entonces, ¿por qué no hacer eso? La pregunta no proporciona ninguna indicación de que algo tenga que pasar el 'StringBuilder' alrededor. –

34

Cuando ya tiene todas las "piezas" que desea añadir, no hay ningún punto en el uso StringBuilder en absoluto. Usando StringBuildery la concatenación de cadenas en la misma llamada según su código de muestra es aún peor.

Esta sería mejor:

return "select id1, " + " id2 " + " from " + " table"; 

En este caso, la concatenación de cadenas que está sucediendo realmente en tiempo de compilación de todos modos, por lo que es equivalente a la aún-más simple:

return "select id1, id2 from table"; 

Usar new StringBuilder().append("select id1, ").append(" id2 ")....toString() realmente dificultará el rendimiento en este caso, porque fuerza la concatenación a realizarse en tiempo de ejecución, en lugar de compilar tiempo. Oops.

Si el código real es la construcción de una consulta SQL mediante la inclusión de valores en la consulta, entonces eso es otra tema aparte, que es que usted debe utilizar consultas parametrizadas, especificando los valores de los parámetros en lugar de en el SQL.

Tengo un article on String/StringBuffer que escribí hace un tiempo - antes de que llegara el StringBuilder. Los principios se aplican a StringBuilder de la misma manera.

4

Tiene razón al suponer que el objetivo del uso del generador de cadenas no se logra, al menos no en toda su extensión.

Sin embargo, cuando el compilador ve la expresión "select id1, " + " id2 " + " from " + " table", emite código que realmente crea un StringBuilder detrás de las escenas y lo agrega, por lo que el resultado final no es tan malo después de todo.

Pero, por supuesto, cualquiera que mire ese código, seguramente pensará que es un poco retardado.

8

[[Hay algunas buenas respuestas aquí, pero me parece que todavía se carece de un bit de información. ]]

return (new StringBuilder("select id1, " + " id2 " + " from " + " table")) 
    .toString(); 

Como usted señala, el ejemplo que da es simplista, pero analicémoslo de todos modos. Lo que sucede aquí es el compilador que realmente hace el trabajo + aquí porque "select id1, " + " id2 " + " from " + " table" son todas constantes. Por lo que este se convierte en:

return new StringBuilder("select id1, id2 from table").toString(); 

En este caso, obviamente, no hay ningún punto en el uso StringBuilder.Es lo mismo que hacer:

// the compiler combines these constant strings 
return "select id1, " + " id2 " + " from " + " table"; 

Sin embargo, incluso si se añade los campos u otros no constantes entonces el compilador utilizaría un internaStringBuilder - no hay necesidad de definir una:

// an internal StringBuilder is used here 
return "select id1, " + fieldName + " from " + tableName; 

Debajo de las sábanas, esto se convierte en código que es aproximadamente equivalente a:

StringBuilder sb = new StringBuilder("select id1, "); 
sb.append(fieldName).append(" from ").append(tableName); 
return sb.toString(); 

realmente la única vez necesita usar StringBuilderdirectamente es cuando tiene el código condicional. Por ejemplo, el código que se parece a la siguiente es desesperada de un StringBuilder:

// 1 StringBuilder used in this line 
String query = "select id1, " + fieldName + " from " + tableName; 
if (where != null) { 
    // another StringBuilder used here 
    query += ' ' + where; 
} 

El + en la primera línea utiliza una instancia StringBuilder. Luego, el += usa otra instancia StringBuilder. Es más eficiente que hacer:

// choose a good starting size to lower chances of reallocation 
StringBuilder sb = new StringBuilder(64); 
sb.append("select id1, ").append(fieldName).append(" from ").append(tableName); 
// conditional code 
if (where != null) { 
    sb.append(' ').append(where); 
} 
return sb.toString(); 

Otra vez que utilizo una StringBuilder es cuando estoy construyendo una cadena de una serie de llamadas a métodos. Entonces puedo crear métodos que toman un argumento StringBuilder:

private void addWhere(StringBuilder sb) { 
    if (where != null) { 
     sb.append(' ').append(where); 
    } 
} 

Cuando está utilizando un StringBuilder, debe ver para cualquier uso de + al mismo tiempo:

sb.append("select " + fieldName); 

Eso + hará que otra StringBuilder interno para ser creado. Esto debe ser, por supuesto:

sb.append("select ").append(fieldName); 

Por último, como @ T.J.rowder señala, siempre se debe hacer una conjetura en el tamaño de la StringBuilder. Esto ahorrará en el número de objetos char[] creados al aumentar el tamaño del búfer interno.

Cuestiones relacionadas