正确的方式来使用StringBuilder

我只是在我的项目中发现了一些像这样的SQL查询构build:

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

这个StringBuilder是否实现了它的目标,即减less内存使用量?

我怀疑这是因为在构造函数中使用了'+'(String concat操作符)。 是否会像使用下面的代码一样使用与String相同的内存量? 我明白,使用StringBuilder.append()时会有所不同。

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

两个语句在内存使用情况下是否相等? 请澄清。

提前致谢!

编辑:

顺便说一句, 这不是我的代码 。 在一个旧的项目中find它。 此外,查询不像我的例子那么小。 🙂

使用StringBuilder的目的,即减less内存。 它实现了吗?

一点都不。 该代码不正确使用StringBuilder 。 (我认为你错误地引用了它,但是确实没有id2table引号)

请注意,目标(通常)是为了减less内存stream失,而不是使用全部内存,使垃圾收集器的生活更轻松。

那要把内存等于使用像下面的string?

不,这会引起更多的记忆扭曲,而不仅仅是你引用的直接连字。 (直到/除非JVM优化器发现代码中显式的StringBuilder是不必要的,并且优化它,如果可以的话)。

如果该代码的作者想要使用StringBuilder (有参数,但也反对;请参阅此答案的结尾注),最好做到这一点(在这里我假设实际上并没有引用id2table ):

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

请注意,我已经在StringBuilder构造函数中列出了some_appropriate_size ,所以它开始具有足够的容量来添加我们要追加的全部内容。 如果你没有指定默认的大小,那么这个大小就是16个字符 ,通常太小,导致StringBuilder必须重新分配才能使自己变得更大(在Sun / Oracle JDK中,IIRC会使自身加倍(或者更多,如果它知道每次耗尽房间时都需要更多的来满足特定的append )。

您可能已经听说,如果使用Sun / Oracle编译器进行编译,string连接在封面下使用StringBuilder 。 这是真的,它将使用一个StringBuilder进行整体expression。 但它会使用默认的构造函数,这意味着在大多数情况下,它将不得不重新分配。 不过,阅读起来很容易。 请注意,这不是 一连串的连接。 所以,例如,这使用一个StringBuilder

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

它大致转化为:

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

所以没关系,虽然默认的构造函数和后续的重新分配是不理想的,但是可能性已经足够好了,而且连接的可读性更高。

但是,这只是一个单一的expression。 多个StringBuilder用于这个:

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

这最终成为这样的事情:

 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; 

…这是非常丑陋的。

但是,重要的是要记住,除了极less数情况外,没有关系 ,并且可读性(这增强了可维护性)是优选的,除了特定的性能问题之外。

当你已经拥有所有你想要追加的“片段”时,完全没有必要使用StringBuilder 。 在你的示例代码中使用StringBuilder string连接在相同的调用中更糟糕。

这会更好:

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

在这种情况下,string连接实际上是在编译时发生的,所以它相当于更简单:

 return "select id1, id2 from table"; 

在这种情况下,使用new StringBuilder().append("select id1, ").append(" id2 ")....toString()实际上会妨碍性能,因为它迫使连接在执行时执行,而不是在编译时。 哎呀。

如果实际代码通过在查询中包含来构buildSQL查询,那么这就是另一个单独的问题,那就是您应该使用参数化查询,并在参数中指定值,而不是在SQL中指定值。

StringBuilder出现之前,我写了一篇关于String / StringBuffer的文章 。 这个原则同样适用于StringBuilder

[[这里有一些很好的答案,但我发现他们还缺乏一点信息。 ]]

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

所以,正如你指出的那样,你给的例子是一个简单的,但是让我们来分析它。 这里发生的事情是编译器实际上在这里做+工作,因为"select id1, " + " id2 " + " from " + " table"都是常量。 所以这变成:

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

在这种情况下,显然,使用StringBuilder没有意义。 你也可以这样做:

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

但是,即使你追加了任何字段或者其他非常量,编译器也会使用一个内部的 StringBuilder – 你不需要定义一个:

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

在封面下,这变成大致相当于:

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

真的是唯一需要直接使用StringBuilder的时候是有条件的代码。 例如,看起来像下面的代码是绝望的一个StringBuilder

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

第一行的+使用一个StringBuilder实例。 然后+=使用另一个StringBuilder实例。 这样做效率更高:

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

另一次我使用StringBuilder的时候,我正在从一些方法调用build立一个string。 然后我可以创build一个StringBuilder参数的方法:

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

当你使用StringBuilder ,你应该同时观察+使用情况:

 sb.append("select " + fieldName); 

+会导致另一个内部的StringBuilder被创build。 这当然应该是:

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

最后,正如@TJrowder指出的,你应该总是猜测StringBuilder的大小。 这将节省增长内部缓冲区大小时创build的char[]对象的数量。

你猜对于使用string生成器的目的是没有达到的,至less不是完全的。

但是,当编译器"select id1, " + " id2 " + " from " + " table"看到expression式"select id1, " + " id2 " + " from " + " table"它发出的代码实际上在后台创build了一个StringBuilder并附加在它后面,所以最终的结果并不是那么糟糕。

但是当然,任何查看这些代码的人都会认为它有些迟钝。

在你发布的代码中,没有任何优势,因为你滥用了StringBuilder。 你在这两种情况下build立相同的string。 使用StringBuilder你可以避免使用append方法对Strings进行+操作。 你应该这样使用它:

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

在Java中,stringtypes是一个不可改变的字符序列,因此,当您添加两个string时,VM会创build一个新的string值,并且两个操作数都是并置的。

StringBuilder提供了一个可变的字符序列,你可以使用它来连接不同的值或variables,而不需要创build新的String对象,所以它有时比使用string更有效率

这提供了一些有用的function,如改变在另一个方法中作为parameter passing的字符序列的内容,这是你不能用string做的。

 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

你也可以使用MessageFormat