Właśnie znalazłem w moim projekcie taką kompilację kwerend sql:
return (new StringBuilder("select id1, " + " id2 " + " from " + " table")).toString();
Czy to StringBuilder
osiąga swój cel, tj. Zmniejsza zużycie pamięci?
Wątpię, ponieważ w konstruktorze zastosowano „+” (operator konkatowania ciągów). Czy zajmie to taką samą ilość pamięci, jak użycie String, jak w kodzie poniżej? Zrozumiałem, różni się przy używaniu StringBuilder.append()
.
return "select id1, " + " id2 " + " from " + " table";
Czy obie instrukcje są równe pod względem użycia pamięci, czy nie? Proszę o wyjaśnienie.
Z góry dziękuję!
Edytować:
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. :)
java
string
stringbuilder
Vaandu
źródło
źródło
PreparedStatement
lub czegoś podobnego: docs.oracle.com/javase/tutorial/jdbc/basics/prepared.htmlOdpowiedzi:
Nie, wcale. Ten kod nie jest używany
StringBuilder
poprawnie. (Myślę jednak, że źle to zacytowałeś; na pewno nie ma wokół cudzysłowuid2
itable
?)Zauważ, że celem (zwykle) jest zmniejszenie utraty pamięci, a nie całkowitej ilości używanej pamięci, aby nieco ułatwić życie odśmiecaczowi pamięci.
Nie, spowoduje to więcej zmian w pamięci niż tylko proste konkatowanie, które zacytowałeś. (Dopóki / chyba że optymalizator JVM zobaczy, że jawny plik
StringBuilder
w kodzie są niepotrzebne i zoptymalizuje je, jeśli to możliwe).Jeśli autor tego kodu chce użyć
StringBuilder
(są argumenty za, ale także przeciw; patrz uwaga na końcu tej odpowiedzi), lepiej zrobić to poprawnie (tutaj zakładam, że w rzeczywistości nie ma cudzysłowówid2
itable
):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
wStringBuilder
konstruktorze, więc zaczyna się z wystarczającą pojemnością dla pełnej zawartości, którą zamierzamy dołączyć. Domyślny rozmiar używany, jeśli nie podasz żadnego, to 16 znaków , co jest zwykle zbyt małe i powodujeStringBuilder
konieczność dokonywania ponownych przydziałów, 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 potrzebyappend
] za każdym razem, gdy zabraknie mu miejsca).Być może słyszeliście, że ciąg konkatenacji będzie używać
StringBuilder
pod kołdrą jeśli skompilowany z kompilatora Sun / Oracle. To prawda, użyje jednegoStringBuilder
dla ogólnego wyrażenia. Ale użyje domyślnego konstruktora, co oznacza, że w większości przypadków będzie musiał dokonać ponownej alokacji. Ale jest łatwiejszy do odczytania. Zauważ, że nie dotyczy to serii konkatenacji. Na przykład ten używa jednegoStringBuilder
:return "prefix " + variable1 + " middle " + variable2 + " end";
To z grubsza przekłada 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 to jest w porządku, chociaż domyślny konstruktor i późniejsza realokacja nie są idealne, szanse są wystarczająco dobre - a konkatenacja jest o wiele bardziej czytelna.
Ale to tylko dla jednego wyrażenia. W
StringBuilder
tym celu używa się wielu s: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.
Należy jednak pamiętać, że we wszystkich, z wyjątkiem nielicznych przypadków , nie ma to znaczenia i preferowana jest czytelność (która zwiększa łatwość konserwacji), poza konkretnym problemem z wydajnością.
źródło
x + y + z
wyrażenia,StringBuilder
chyba że miałbym dobry powód, by podejrzewać, że będzie to poważny problem.StringBuilder sql = new StringBuilder(" XXX); sql.append("nndmn");...
. Podobnesql.append
linie mają około 60 linii. Czy to w porządku?StringBuilder
początkowo zostanie przydzielone wystarczająco dużo miejsca na napis, który przekazałeś konstruktorowi, plus 16 znaków. Więc jeśli dodasz więcej niż 16 znaków (śmiem twierdzić, że tak jest, jeśli jest 60 dopisków!),StringBuilder
Będziesz musiał ponownie przydzielić co najmniej raz, a być może wiele razy. Jeśli masz rozsądny pomysł, jak duży będzie wynik końcowy (powiedzmy 400 znaków), najlepiej zróbsql = new StringBuilder(400);
(lub cokolwiek), a następnie wykonaj czynnościappend
.StringBuilder
że z góry zaoszczędzi około ośmiu realokacji pamięci (zakładając, że początkowy ciąg miał około 10 znaków, SB na początku miałby 26, a następnie podwoił się do 52, a następnie 104, 208, 416, 832, 1664, 3328 i wreszcie 6656). Istotne tylko, jeśli jest to hotspot, ale nadal, jeśli wiesz z góry ... :-)Kiedy masz już wszystkie „elementy”, które chcesz dołączyć, nie ma sensu ich używać
StringBuilder
. UżywanieStringBuilder
i konkatenacja ciągów znaków w tym samym wywołaniu, co w przykładowym kodzie, jest jeszcze gorsze.Tak byłoby lepiej:
return "select id1, " + " id2 " + " from " + " table";
W tym przypadku konkatenacja ciągów i tak ma miejsce w czasie kompilacji , więc jest to odpowiednik jeszcze prostszego:
return "select id1, id2 from table";
Użycie w
new StringBuilder().append("select id1, ").append(" id2 ")....toString()
rzeczywistości utrudni wydajność w tym przypadku, ponieważ wymusza konkatenację w czasie wykonywania , a nie w czasie kompilacji . Ups.Jeśli rzeczywisty kod tworzy zapytanie SQL poprzez uwzględnienie wartości w zapytaniu, to jest to kolejna osobna kwestia kwestia, która polega na tym, że należy używać zapytań sparametryzowanych, określając wartości w parametrach, a nie w języku SQL.
Mam artykuł na
String
/StringBuffer
który napisałem jakiś czas temu - zanim sięStringBuilder
pojawił. Zasady mają jednak zastosowanieStringBuilder
w ten sam sposób.źródło
[[Jest tu kilka dobrych odpowiedzi, ale wydaje mi się, że nadal brakuje im trochę informacji. ]]
return (new StringBuilder("select id1, " + " id2 " + " from " + " table")) .toString();
Jak więc zauważyłeś, podany przykład jest uproszczony, ale i tak go przeanalizujmy. To, co dzieje się tutaj, to fakt, że kompilator faktycznie
+
działa tutaj, ponieważ"select id1, " + " id2 " + " from " + " table"
wszystkie 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 nawet jeśli dołączałeś jakiekolwiek pola lub inne zmienne nie będące stałymi, kompilator użyłby wewnętrznego
StringBuilder
- nie ma potrzeby, abyś go definiował:// an internal StringBuilder is used here return "select id1, " + fieldName + " from " + tableName;
Pod okładkami zamienia się to w kod, który jest w przybliżeniu równoważny z:
StringBuilder sb = new StringBuilder("select id1, "); sb.append(fieldName).append(" from ").append(tableName); return sb.toString();
Naprawdę jedyny czas, którego potrzebujesz do
StringBuilder
bezpośredniego użycia, to kod warunkowy. Na przykład kod, który wygląda jak poniższy, desperacko szukaStringBuilder
:// 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 jednejStringBuilder
instancji. Następnie+=
używa innejStringBuilder
instancji. Bardziej efektywne jest:// 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 używam a,
StringBuilder
kiedy buduję ciąg z wielu wywołań metod. Następnie mogę stworzyć 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);
Że
+
spowoduje kolejny wewnętrznyStringBuilder
ma zostać utworzony. Powinno to oczywiście być:sb.append("select ").append(fieldName);
Na koniec, jak wskazuje @TJrowder, zawsze powinieneś zgadywać rozmiar
StringBuilder
. Pozwoli to zaoszczędzić na liczbiechar[]
obiektów utworzonych podczas zwiększania rozmiaru bufora wewnętrznego.źródło
Masz rację, domyślając się, że cel używania konstruktora stringów nie został osiągnięty, przynajmniej nie w pełnym zakresie.
Jednak gdy kompilator widzi wyrażenie
"select id1, " + " id2 " + " from " + " table"
, emituje kod, który faktycznie tworzyStringBuilder
za kulisami i dołącza do niego, więc ostateczny wynik nie jest taki zły.Ale oczywiście każdy, kto patrzy na ten kod, z pewnością pomyśli, że jest trochę opóźniony.
źródło
W opublikowanym kodzie nie byłoby żadnych korzyści, ponieważ niewłaściwie używasz StringBuilder. W obu przypadkach budujesz ten sam ciąg. Używając StringBuilder, możesz uniknąć
+
operacji na Strings przy użyciuappend
metody. Powinieneś go używać w ten sposób:return new StringBuilder("select id1, ").append(" id2 ").append(" from ").append(" table").toString();
W Javie typ String to niezmienna sekwencja znaków, więc po dodaniu dwóch ciągów maszyna wirtualna tworzy nową wartość String z połączonymi obydwoma operandami.
StringBuilder zapewnia zmienną sekwencję znaków, której można użyć do łączenia różnych wartości lub zmiennych bez tworzenia nowych obiektów String, dzięki czemu czasami może być bardziej wydajna niż praca z ciągami znaków
Zapewnia to kilka przydatnych funkcji, takich jak zmiana zawartości sekwencji znaków przekazanych jako parametr wewnątrz innej metody, czego nie można zrobić w przypadku 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); }
Więcej informacji na http://docs.oracle.com/javase/tutorial/java/data/buffers.html
źródło
+
, który i tak zostanie przekonwertowany na ten sam kod.StringBuilder
jest przydatne, gdy nie można wykonać całej konkatenacji w jednym wyrażeniu, ale nie w tym przypadku.StringBuilder
, gdybyś użyłreturn "select id1, " + foo + "something else" + bar;
- więc dlaczego tego nie zrobić? Pytanie nie wskazuje na to, że cokolwiek musi zostać omówioneStringBuilder
.Można również użyć MessageFormat zbyt
źródło