Poprawny sposób użycia StringBuilder w SQL

Właśnie znalazłem w moim projekcie taki build zapytań sql:

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

Czy to StringBuilder osiąga swój cel, czyli zmniejsza zużycie pamięci?

Wątpię, ponieważ w konstruktorze używany jest ' + ' (String Concat operator). Czy zajmie to taką samą ilość pamięci, jak użycie ciągu Jak poniższy kod? s Rozumiem, różni się przy użyciu StringBuilder.append().

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

Czy oba wyrażenia są równe w zużyciu pamięci czy nie? Proszę wyjaśnić.

Dzięki w naprzód!

Edit:

BTW, to nie jest mój kod. Znalazłem to w starym projekcie. Ponadto zapytanie nie jest tak małe, jak w moim przykładzie. :)

Author: ojonugwa ochalifu, 2012-01-04

6 answers

Celem zastosowania Stringbuildera, czyli redukcji pamięci. Czy został osiągnięty?

Nie, wcale nie. Ten kod nie używa StringBuilder poprawnie. (Myślę, że źle to cytujesz; na pewno nie ma cytatów wokół id2 i table?)

Zauważ ,że celem (zazwyczaj) jest zmniejszenie pamięci zamiast całkowitej pamięci używanej, aby ułatwić życie na garbage collector.

Czy to zajmie pamięć równą używaniu String like na dole?

Nie, to spowoduje więcej utraty pamięci niż tylko prosty konkat, który zacytowałeś. (Until / unless JVM optimizer widzi, że jawny StringBuilder w kodzie jest niepotrzebny i optymalizuje go, jeśli może.)

Jeśli autor tego kodu chce użyć StringBuilder (są argumenty za, ale i przeciw; patrz uwaga na końcu tej odpowiedzi), lepiej zrobić to poprawnie (tu zakładam, że nie ma cudzysłowów wokół id2 i table):

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

Zauważ, że wymieniłem some_appropriate_size w konstruktorze StringBuilder, tak aby zaczynał się z wystarczającą pojemnością dla pełnej zawartości, którą zamierzamy dołączyć. Domyślnym rozmiarem używanym, jeśli go nie podasz, jest 16 znaków, który jest zwykle zbyt mały i powoduje, że StringBuilder musi dokonać realokacji, aby się powiększyć (IIRC, w Sun/Oracle JDK, podwaja się [lub więcej, jeśli wie, że potrzebuje więcej, aby zaspokoić określone append] za każdym razem, gdy kończy się miejsce).

Być może słyszałeś, że string concatenation będzie używać StringBuilder pod okładkami, jeśli zostanie skompilowany z kompilatorem Sun/Oracle. To prawda, użyje jednego StringBuilder dla ogólnego wyrażenia. Ale użyje domyślnego konstruktora, co oznacza, że w większości przypadków będzie musiał dokonać realokacji. Jest jednak łatwiejszy do odczytania. Zauważ, że jest to Nie prawda z serii konkatenacji. Więc na przykład, To używa jednego StringBuilder:

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

To w przybliżeniu tłumaczy się na:

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

Więc w porządku, chociaż domyślny konstruktor i późniejsze realokacje nie są idealne, szanse są wystarczająco dobre - a konkatenacja jest lot bardziej czytelny.

Ale to tylko dla jednego wyrażenia. Wiele StringBuilder s są używane do tego celu:

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

To kończy się czymś takim:

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;

...co jest dość brzydkie.

Ważne jest, aby pamiętać, jednak, że we wszystkich, ale bardzo niewielu przypadkach to nie ma znaczenia i idąc z czytelności (co zwiększa łatwość konserwacji) jest preferowane z wyjątkiem konkretnego problemu wydajności.

 178
Author: T.J. Crowder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-04 12:10:38

Jeśli masz już wszystkie "kawałki", które chcesz dołączyć, nie ma sensu używać StringBuilder w ogóle. Za pomocą StringBuilder i konkatenacja ciągu w tym samym wywołaniu, jak na przykładowym kodzie jest jeszcze gorsza.

Tak byłoby lepiej:

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

W tym przypadku konkatenacja łańcuchów rzeczywiście dzieje się w compile-time w każdym razie, więc jest to odpowiednik nawet prostszego:

return "select id1, id2 from table";

Użycie new StringBuilder().append("select id1, ").append(" id2 ")....toString() w rzeczywistości utrudni wydajność w tym przypadku, ponieważ wymusza ona wykonanie konkatenacji w czasie wykonania , zamiast w czasie kompilacji. UPS.

Jeśli prawdziwy kod buduje zapytanie SQL przez włączenie wartości do zapytania, to jest to kolejna oddzielna sprawa , która polega na tym, że powinieneś używać zapytań sparametryzowanych, określając wartości w parametrach, a nie w SQL.

Mam artykuł o String / StringBuffer które napisałem jakiś czas temu-przed StringBuilder przyszedł. Zasady mają zastosowanie do StringBuilder w ten sam sposób.

 37
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-01 18:01:31

[[jest kilka dobrych odpowiedzi tutaj, ale uważam, że nadal brakuje im trochę informacji. ]]

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

Więc jak zauważyłeś, przykład, który podałeś, jest uproszczony, ale przeanalizujmy go i tak. To, co się tutaj dzieje, to kompilator faktycznie działa +, ponieważ "select id1, " + " id2 " + " from " + " table" są stałymi. Więc to zamienia się w:

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

W tym przypadku, oczywiście, nie ma sensu używać StringBuilder. Równie dobrze możesz:

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

Jednakże, nawet jeśli w przypadku dodawania dowolnych pól lub innych nie-stałych kompilator użyłby wewnętrznego StringBuilder -- nie ma potrzeby definiowania jednego:

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

Pod okładkami, to zamienia się w kod, który jest w przybliżeniu odpowiednikiem:

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

Naprawdę jedyny czas, który musisz wykorzystać StringBuilder bezpośrednio jest wtedy, gdy masz kod warunkowy. Na przykład kod, który wygląda jak poniżej, jest rozpaczliwy dla StringBuilder:

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

+ w pierwszej linii używa jedna StringBuilder instancja. Następnie += używa innej instancji StringBuilder. Jest to bardziej efektywne do zrobienia:

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

Innym razem, kiedy używam {[12] } jest budowanie ciągu znaków z wielu wywołań metod. Następnie mogę tworzyć metody, które przyjmują StringBuilder argument:

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

Kiedy używasz StringBuilder, powinieneś uważać na jakiekolwiek użycie + w tym samym czasie:

sb.append("select " + fieldName);

To + spowoduje powstanie kolejnego wewnętrznego StringBuilder. Powinno to oczywiście be:

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

Na koniec, jak zauważa @T. J. rowder, zawsze powinieneś zgadywać rozmiar StringBuilder. Spowoduje to zapisanie liczby char[] obiektów utworzonych podczas zwiększania rozmiaru wewnętrznego bufora.

 10
Author: Gray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-06 00:29:57

Masz rację, zgadując, że cel użycia string builder nie został osiągnięty, przynajmniej nie w pełnym zakresie.

Jednakże, gdy kompilator widzi wyrażenie "select id1, " + " id2 " + " from " + " table" emituje kod, który faktycznie tworzy StringBuilder Za kulisami i dołącza do niego, więc efekt końcowy nie jest taki zły.

Ale oczywiście każdy, kto spojrzy na ten kod, pomyśli, że jest trochę opóźniony.

 4
Author: Mike Nakis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-31 15:49:03

W kodzie, który napisałeś, nie byłoby żadnych korzyści, ponieważ nadużywasz Stringbuildera. W obu przypadkach tworzysz ten sam ciąg znaków. Używając StringBuilder możesz uniknąć operacji + na łańcuchach używając metody append. Należy go używać w ten sposób:

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

W języku Java typ String jest niezniszczalną sekwencją znaków, więc gdy dodasz dwa ciągi znaków, maszyna wirtualna utworzy nową wartość łańcucha z połączonymi obydwoma operandami.

StringBuilder zapewnia zmienną sekwencję znaki, których można użyć do łączenia różnych wartości lub zmiennych bez tworzenia nowych obiektów łańcuchowych, a więc czasami może być bardziej wydajny niż praca z łańcuchami

Zapewnia to kilka przydatnych funkcji, takich jak zmiana zawartości sekwencji znaków przekazywanej jako parametr w innej metodzie, czego nie można zrobić z łańcuchami znaków.

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

Więcej informacji na http://docs.oracle.com/javase/tutorial/java/data/buffers.html

 2
Author: Tomas Narros,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-04 11:27:03

Możesz również użyć MessageFormat również

 1
Author: JGFMK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-15 08:56:23