SQL'de StringBuilder'ı kullanmanın doğru yolu


88

Projemde bunun gibi bir sql sorgu yapısı buldum:

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

Bu StringBuilder, bellek kullanımını azaltmak gibi amacına ulaşıyor mu?

Bundan şüpheliyim, çünkü yapıcıda '+' (String concat operatörü) kullanılıyor. Bu, String'i kullanmakla aşağıdaki kodla aynı miktarda bellek alacak mı? Anladım, kullanırken farklılık gösteriyor StringBuilder.append().

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

Her iki ifade de bellek kullanımında eşit mi, değil mi? Lütfen açıkla.

Şimdiden teşekkürler!

Düzenle:

BTW, bu benim kodum değil . Eski bir projede buldum. Ayrıca sorgu, örneğimdeki kadar küçük değil. :)


1
SQL GÜVENLİĞİ: her zaman kullanın PreparedStatementveya benzer bir şey kullanın : docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
Christophe Roussy

Bellek kullanımı dışında neden bunun yerine bir SQL oluşturucu kitaplığı kullanmıyorsunuz: stackoverflow.com/q/370818/521799
Lukas Eder

Yanıtlar:


182

StringBuilder kullanmanın amacı, yani belleği azaltmak. Başarıldı mı?

Hayır, hiç de değil. Bu kod StringBuilderdoğru kullanılmıyor . (Yine de yanlış alıntı yaptığınızı düşünüyorum; kesinlikle etrafta alıntı yoktur id2ve table?)

Amacın (genellikle) kullanılan toplam bellek yerine bellek karmaşasını azaltmak , çöp toplayıcıdaki hayatı biraz daha kolaylaştırmak olduğunu unutmayın.

Bu, belleği aşağıdaki gibi String kullanmaya eşit mi alacak?

Hayır, alıntı yaptığınız düz concat'tan daha fazla bellek karmaşasına neden olur . (JVM iyileştirici StringBuilderkoddaki açık öğenin gereksiz olduğunu görene ve yapabiliyorsa onu optimize edene kadar / sürece .)

Bu kodun yazarı kullanmak istiyorsa StringBuilder(bunun için argümanlar var, ama aynı zamanda aleyhte; bu cevabın sonundaki nota bakın), bunu doğru şekilde yapmak daha iyi (burada aslında alıntıların olmadığını varsayıyorum id2ve table):

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

Yapıcıda listelediğime dikkat some_appropriate_sizeedin StringBuilder, böylece ekleyeceğimiz tüm içerik için yeterli kapasite ile başlasın. Birini belirtmezseniz kullanılan varsayılan boyut 16 karakterdir ve bu genellikle çok küçüktür ve StringBuilderkendini büyütmek için yeniden tahsis yapmak zorunda kalmaya neden olur (IIRC, Sun / Oracle JDK'da, kendini ikiye katlar [veya belirli bir append] için daha fazlasına ihtiyaç duyduğunu bilir ], her seferinde oda bittiğinde).

Sen dize birleştirme olduğunu duymuş olabilirsiniz edecek bir kullanmak StringBuilderGüneş / Oracle derleyici ile derlenmiş eğer örtülerin altında. Bu doğrudur, StringBuildergenel ifade için birini kullanacaktır . Ancak varsayılan kurucuyu kullanacaktır, bu da çoğu durumda yeniden tahsis yapması gerektiği anlamına gelir. Yine de okumak daha kolay. Bunun bir dizi birleştirme için doğru olmadığını unutmayın . Örneğin, bu bir tane kullanır :StringBuilder

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

Kabaca şu anlama gelir:

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

Öyleyse sorun değil, varsayılan kurucu ve sonraki yeniden tahsis (ler) ideal olmasa da, olasılıklar yeterince iyi - ve birleştirme çok daha okunaklı.

Ancak bu yalnızca tek bir ifade içindir. Bunun için birden çok StringBuilderURL kullanılır:

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

Bu, şöyle bir şeye dönüşür:

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;

... ki bu oldukça çirkin.

Bununla birlikte, çok azı dışında hepsinde önemli olmadığını ve belirli bir performans sorunu dışında okunabilirlikle devam etmenin (sürdürülebilirliği artıran) tercih edildiğini hatırlamak önemlidir .


Doğru, bu daha iyi. Parametresiz kurucunun kullanımı biraz talihsizdir, ancak önemli olma ihtimali düşüktür. Önemli bir sorun olacağından şüphelenmek için iyi bir nedenim olmadığı sürece, yine de tek bir x + y + zifade kullanırım StringBuilder.
Jon Skeet

@Crowder'ın bir şüphesi daha var. StringBuilder sql = new StringBuilder(" XXX); sql.append("nndmn");.... Benzer sql.appendhatlar yaklaşık 60 satırdır. Bu iyi mi?
Vaandu

1
@Vanathi: ("Soru", "şüphe" değil - bu yaygın bir yanlış çeviri.) Sorun değil, ancak muhtemelen birden fazla yeniden tahsisle sonuçlanacaktır, çünkü StringBuilderbaşlangıçta kurucuyu geçtiğiniz dizge artı 16 karakter için yeterli alan tahsis edilecektir. Bu nedenle, 16 karakterden fazlasını eklerseniz (60 ek varsa, StringBuildertahmin ediyorum!), En az bir kez ve muhtemelen birçok kez yeniden tahsis etmek zorunda kalacaktır. Son sonucun ne kadar büyük olacağına dair makul bir fikriniz varsa (örneğin, 400 karakter), en iyisi yapmak sql = new StringBuilder(400);(veya her neyse) o zaman appends yapmaktır .
TJ Crowder

@Vanathi: Yardımcı olduğuna sevindim. Evet, 6.000 karakter olacaksa, StringBuilderönceden yaklaşık sekiz bellek yeniden tahsisini kaydedeceğini söylersek (başlangıç ​​dizesinin yaklaşık 10 karakter olduğu varsayılarak, SB başlangıçta 26 olacak, sonra ikiye katlanarak 52, sonra 104, 208, 416, 832, 1664, 3328 ve son olarak 6656). Sadece bu bir sıcak nokta ise önemlidir, ancak yine de önceden biliyorsanız ... :-)
TJ Crowder

@TJ Crowder, daha iyi performans için "+" operatörünü kullanmamam gerektiğini söylüyorsunuz. sağ? Öyleyse Oracal neden kendi dillerine "+" operatörünü ekledi? Cevabınız için herhangi bir şekilde oyumu açıklar mısınız?
Smit Patel

38

Eklemek istediğiniz tüm "parçalara" zaten sahip olduğunuzda, kullanmanın hiçbir anlamı yoktur StringBuilder. Örnek kodunuza göre aynı çağrıda StringBuilder ve dize birleştirmeyi kullanmak daha da kötüdür.

Bu daha iyi olurdu:

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

Bu durumda, dize birleştirme aslında zaten derleme zamanında oluyor , bu yüzden daha da basit olana eşdeğer:

return "select id1, id2 from table";

Bu durumda kullanmak new StringBuilder().append("select id1, ").append(" id2 ")....toString(), aslında performansı engelleyecektir , çünkü birleştirmeyi derleme zamanı yerine yürütme zamanında gerçekleştirmeye zorlar . Oops.

Gerçek kod, sorguya değerler ekleyerek bir SQL sorgusu oluşturuyorsa , bu başka bir ayrı sorundur, yani parametreleştirilmiş sorgular kullanmanız, değerleri SQL yerine parametrelerde belirtmeniz gerekir.

Bir var makalesine String/StringBuffer önce - Bir süre önce yazdığı StringBuilderortaya çıktı. İlkeler StringBuilderaynı şekilde geçerlidir .


10

[[Burada bazı iyi cevaplar var ama hala biraz bilgiden yoksun olduklarını görüyorum. ]]

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

Sizin de belirttiğiniz gibi, verdiğiniz örnek basit ama yine de analiz edelim. Burada olan şey, derleyicinin aslında buradaki +işi yapmasıdır çünkü "select id1, " + " id2 " + " from " + " table"hepsi sabittir. Yani bu şuna dönüşüyor:

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

Bu durumda, elbette, kullanmanın bir anlamı yok StringBuilder. Sen de yapabilirsin:

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

Ancak, herhangi bir alanı veya sabit olmayan başka bir alanı ekliyor olsanız bile, derleyici bir dahili kullanır - bir StringBuildertane tanımlamanıza gerek yoktur:

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

Kapakların altında bu, yaklaşık olarak aşağıdakilere eşdeğer bir koda dönüşür:

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

Gerçekten StringBuilder doğrudan kullanmanız gereken tek zaman koşullu kodunuz olduğu zamandır . Örneğin, aşağıdaki gibi görünen kod, bir için çaresizdir StringBuilder:

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

+İlk hat içinde bir kullanan StringBuilderörneği. Sonra +=başka bir StringBuilderörnek kullanır . Şunları yapmak daha etkilidir:

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

A'yı kullandığım başka bir zaman, StringBuilderbir dizi yöntem çağrısından bir dize oluşturduğum zamandır . Sonra StringBuilderargüman alan yöntemler oluşturabilirim :

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

A kullanırken , aynı anda StringBuilderherhangi bir kullanım için izlemelisiniz +:

sb.append("select " + fieldName);

Bu +, başka bir içsel StringBuilderyaratılmasına neden olacaktır . Bu elbette şöyle olmalıdır:

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

Son olarak, @TJrowder'ın işaret ettiği gibi, her zaman StringBuilder. Bu char[], dahili tamponun boyutunu büyütürken oluşturulan nesnelerin sayısından tasarruf sağlayacaktır .


4

Dize oluşturucuyu kullanmanın amacına en azından tam anlamıyla ulaşılmadığını tahmin etmekte haklısınız.

Bununla birlikte, derleyici ifadeyi gördüğünde, "select id1, " + " id2 " + " from " + " table"aslında StringBuilderarka planda bir sahne yaratan ve ona eklenen bir kod yayar , dolayısıyla sonuç o kadar da kötü olmaz.

Ama elbette bu koda bakan herhangi biri bunun bir tür gecikmiş olduğunu düşünmek zorunda.


2

StringBuilder'ı kötüye kullandığınız için gönderdiğiniz kodun hiçbir avantajı olmayacaktır. Her iki durumda da aynı dizeyi oluşturursunuz. StringBuilder +kullanarak, appendyöntemi kullanarak Strings üzerinde işlem yapılmasını önleyebilirsiniz . Bunu şu şekilde kullanmalısın:

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

Java'da, String türü değişmez bir karakter dizisidir, bu nedenle iki Strings eklediğinizde VM, her iki işleneni birleştirerek yeni bir String değeri oluşturur.

StringBuilder, yeni String nesneleri oluşturmadan farklı değerleri veya değişkenleri birleştirmek için kullanabileceğiniz değiştirilebilir bir karakter dizisi sağlar ve böylece bazen dizelerle çalışmaktan daha verimli olabilir

Bu, Strings ile yapamayacağınız başka bir yöntemin içinde parametre olarak geçirilen bir char dizisinin içeriğini değiştirmek gibi bazı yararlı özellikler sağlar.

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

Http://docs.oracle.com/javase/tutorial/java/data/buffers.html adresinde daha fazla bilgi


1
Hayır, yapmamalısın. Kullanmaktan daha az okunabilir +, zaten aynı koda dönüştürülecek. StringBuildertek bir ifadede tüm birleştirmeyi gerçekleştiremediğinizde, ancak bu durumda gerçekleştiremediğinizde kullanışlıdır.
Jon Skeet

1
Sorudaki dizenin örnek olarak gönderildiğini anlıyorum. Bunun gibi "sabit" bir dizge oluşturmak ne StringBuilder ile ne de farklı parçalar eklemek mantıklı olmaz, çünkü bunu tek bir sabit "tablodan id1, id2'yi seçin"
Tomas Narros

Ancak değişkenlerden sabit olmayan değerler olsa bile, kullanacak olsaydınız yine de tek bir tane StringBuilderkullanırdı return "select id1, " + foo + "something else" + bar;- öyleyse bunu neden yapmayasınız? Soru, herhangi bir şeyin StringBuilderetrafta dolaşması gerektiğine dair hiçbir gösterge sağlamaz .
Jon Skeet

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.