web-dev-qa-db-fra.com

Manière correcte d'utiliser StringBuilder dans SQL

Je viens de trouver des requêtes SQL de ce type dans mon projet:

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

Est-ce que StringBuilder atteint son objectif, à savoir réduire l'utilisation de la mémoire?

Je doute que, parce que dans le constructeur, le '+' (opérateur de concaténage) est utilisé. Est-ce que cela prendra la même quantité de mémoire que d'utiliser String comme le code ci-dessous? s J'ai compris, cela diffère lorsque vous utilisez StringBuilder.append().

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

Les deux instructions correspondent-elles ou non à l'utilisation de la mémoire? Précisez s'il vous plaît.

Merci d'avance!

Modifier:

BTW, ce n'est pas mon code. Je l'ai trouvé dans un ancien projet. En outre, la requête n'est pas si petite que celle de mon exemple. :)

88
Vaandu

L’utilisation de StringBuilder, c’est-à-dire réduire la mémoire. Est-ce atteint?

Non pas du tout. Ce code n'utilise pas StringBuilder correctement. (Je pense cependant que vous avez mal cité; il n'y a sûrement pas de citations autour de id2 et table?)

Notez que le but (généralement) est de réduire la mémoire le taux de désabonnement plutôt que la mémoire totale utilisée, afin de simplifier un peu la vie du récupérateur de place.

Cela prendra-t-il autant de mémoire que d'utiliser String comme ci-dessous?

Non, cela provoquera plus de pertes de mémoire que le simple concat que vous avez cité. (Jusqu'à/sauf si l'optimiseur JVM voit que le StringBuilder explicite du code n'est pas nécessaire et l'optimise s'il le peut.)

Si l'auteur de ce code veut utiliser StringBuilder (il y a des arguments pour, mais aussi contre; voir la note à la fin de cette réponse), il est préférable de le faire correctement (ici, je suppose qu'il n'y en a pas réellement). guillemets autour de id2 et table):

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

Notez que j'ai répertorié some_appropriate_size dans le constructeur StringBuilder, de sorte qu'il ait au départ une capacité suffisante pour tout le contenu que nous allons ajouter. La taille par défaut utilisée si vous n'en spécifiez pas est 16 caractères , ce qui est généralement trop petit et oblige la StringBuilder à effectuer des réaffectations pour devenir plus grande (IIRC, in the Sun/JDK Oracle, il se double (ou plus, s'il sait qu'il en a besoin de plus pour satisfaire un append] spécifique chaque fois qu'il manque de place).

Vous avez peut-être entendu dire que la concaténation de chaînes utilisera un StringBuilder sous les couvertures si elle est compilée avec le compilateur Sun/Oracle. Ceci est vrai, il utilisera un StringBuilder pour l'expression globale. Mais il utilisera le constructeur par défaut, ce qui signifie que dans la majorité des cas, il devra procéder à une réallocation. C'est plus facile à lire, cependant. Notez que ceci n'est pas vrai d'une série de concaténations. Ainsi, par exemple, ceci utilise un StringBuilder:

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

Cela se traduit approximativement par:

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

Donc, ça va, même si le constructeur par défaut et les réaffectations ultérieures ne sont pas idéales, il y a de fortes chances que ce soit assez bon - et la concaténation est un lot plus lisible.

Mais ce n'est que pour une seule expression. Plusieurs StringBuilders sont utilisés pour cela:

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

Cela finit par devenir quelque chose comme ceci:

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;

... ce qui est assez moche.

Il est important de se rappeler, cependant, que dans presque tous les cas , cela n'a pas d'importance et qu'il est préférable d'utiliser la lisibilité (ce qui améliore la maintenabilité) un problème de performance spécifique.

182
T.J. Crowder

Quand vous avez déjà tous les "morceaux" que vous souhaitez ajouter, utiliser StringBuilder ne sert à rien. L'utilisation de StringBuilder et de la concaténation de chaînes dans le même appel, conformément à votre exemple de code, est encore pire.

Ce serait mieux:

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

Dans ce cas, la concaténation de chaînes a lieu à au moment de la compilation de toute façon, ce qui revient au modèle encore plus simple:

return "select id1, id2 from table";

Utiliser new StringBuilder().append("select id1, ").append(" id2 ")....toString() va réellement nuire aux performances dans ce cas, car il oblige la concaténation à être exécutée à heure d'exécution , au lieu de compiler heure. Oops.

Si le code réel construit une requête SQL en incluant des valeurs dans la requête, il s'agit d'un autre problème séparé , à savoir que vous devrait utiliser des requêtes paramétrées, en spécifiant les valeurs dans les paramètres plutôt que dans le code SQL.

J'ai un article sur String/StringBuffer que j'ai écrit il y a quelque temps - avant que StringBuilder ne se présente. Les principes s’appliquent à StringBuilder de la même manière cependant.

38
Jon Skeet

[[Il y a quelques bonnes réponses ici mais je trouve qu'il manque encore un peu d'informations. ]]

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

Comme vous le soulignez, l'exemple que vous donnez est simpliste, mais analysons-le quand même. Ce qui se passe ici, c'est que le compilateur fait en réalité que le + fonctionne ici parce que "select id1, " + " id2 " + " from " + " table" sont toutes des constantes. Donc cela devient:

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

Dans ce cas, évidemment, il est inutile d'utiliser StringBuilder. Vous pourriez aussi bien faire:

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

Cependant, même si vous ajoutiez des champs ou d'autres non-constantes, le compilateur utiliserait un interneStringBuilder - il n'est pas nécessaire que vous en définissiez un:

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

Sous les couvertures, cela se transforme en un code à peu près équivalent à:

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

Le seul moment où vous devez utiliser StringBuilderdirectement est lorsque vous avez du code conditionnel. Par exemple, un code ressemblant à ce qui suit est désespéré pour un StringBuilder:

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

Le + dans la première ligne utilise une instance StringBuilder. Ensuite, le += utilise une autre instance de StringBuilder. Il est plus efficace de faire:

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

Une autre fois que j'utilise un StringBuilder, c'est quand je construis une chaîne à partir de plusieurs appels de méthodes. Ensuite, je peux créer des méthodes qui prennent un argument StringBuilder:

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

Lorsque vous utilisez un StringBuilder, vous devez surveiller en même temps toute utilisation de +:

sb.append("select " + fieldName);

Ce + provoquera la création d'un autre StringBuilder interne. Cela devrait bien sûr être:

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

Enfin, comme le souligne @ T.J.rowder, vous devez toujours deviner la taille de la variable StringBuilder. Cela permettra d'économiser sur le nombre d'objets char[] créés lors de l'augmentation de la taille du tampon interne.

10
Gray

Vous avez raison de supposer que l'objectif d'utilisation du constructeur de cordes n'est pas atteint, du moins pas dans toute son étendue.

Cependant, lorsque le compilateur voit l'expression "select id1, " + " id2 " + " from " + " table", il émet un code qui crée en réalité un StringBuilder en arrière-plan et y est ajouté. Le résultat final n'est donc pas si mauvais après tout.

Bien entendu, tous ceux qui consultent ce code sont forcés de penser qu'il est en quelque sorte retardé.

4
Mike Nakis

Dans le code que vous avez posté, il n'y aurait aucun avantage à utiliser abusivement StringBuilder. Vous construisez la même chaîne dans les deux cas. À l'aide de StringBuilder, vous pouvez éviter l'opération + sur les chaînes à l'aide de la méthode append. Vous devriez l'utiliser de cette façon:

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

En Java, le type String est une séquence immuable de caractères. Ainsi, lorsque vous ajoutez deux chaînes, la VM crée une nouvelle valeur String avec les deux opérandes concaténés.

StringBuilder fournit une séquence mutable de caractères, que vous pouvez utiliser pour concaténer différentes valeurs ou variables sans créer de nouveaux objets String. Cela peut donc parfois être plus efficace que de travailler avec des chaînes.

Cela fournit des fonctionnalités utiles, comme changer le contenu d'une séquence de caractères passée en paramètre dans une autre méthode, ce que vous ne pouvez pas faire avec 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);
}

Plus d'infos sur http://docs.Oracle.com/javase/tutorial/Java/data/buffers.html

2
Tomas Narros

Vous pouvez aussi utiliser MessageFormat aussi

1
JGFMK