我刚刚在我的项目中发现了一些像这样的sql查询构建。
return (new StringBuilder("select id1, " + " id2 " + " from " + " table")).toString();
这个 "StringBuilder "是否达到了它的目的,即减少内存使用?
我很怀疑,因为在构造函数中使用了'+'(String concat操作符)。这是否会像下面的代码一样使用字符串占用同样多的内存?
return "select id1, " + " id2 " + " from " + " table";
这两个语句的内存占用量是否相等?请澄清一下。
提前感谢!
编辑。
BTW,这不是我的代码。在一个旧项目中发现的。另外,这个查询并不像我的例子中的那么小。)
使用StringBuilder的目的,即减少内存。是否达到了?
没有,完全没有。那段代码没有正确使用StringBuilder
。(我认为你引用错了,id2'和
table'周围肯定没有引号?)
请注意,目的(通常)是减少内存流失,而不是使用的总内存,以使垃圾收集器的工作更容易。
这样做所占用的内存是否与使用下面的String相同?
不,它比你引用的直接连接会造成*多的内存流失。(除非JVM优化器发现代码中明确的`StringBuilder'是不必要的,并将其优化掉,如果可以的话。)
如果该代码的作者想使用StringBuilder'(有支持的论点,但也有反对的论点;见本答案末尾的注释),最好正确地使用(这里我假设
id2'和`table'周围实际上没有引号)。
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
不得不做重新分配来使自己变大(IIRC,在Sun/Oracle JDK中,每次当它的空间用完时,它都会把自己翻倍[或更多,如果它知道它需要更多来满足特定的append
])。
你可能听说过,如果用Sun/Oracle编译器编译的话,字符串连接将使用一个StringBuilder'。这是真的,它将为整个表达式使用一个
StringBuilder'。但是它将使用默认的构造函数,这意味着在大多数情况下,它必须进行重新分配。不过,这更容易阅读。请注意,这不是系列*串联的真实情况。因此,例如,这使用了一个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();
所以没关系,虽然默认的构造函数和随后的重新分配(s)并不理想,但胜在足够好 —而且串联的可读性也**了。
但这只是针对单个表达式。多个 "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;
...这是很难看的。
不过,重要的是要记住,除了极少数情况外,它并不重要,除非有特殊的性能问题,否则最好选择可读性(能提高可维护性)。
当你已经有了所有你想追加的"件",使用`StringBuilder'根本没有意义。在同一调用中使用 "StringBuilder "和字符串连接,就像你的示例代码一样,甚至更糟。
这样会更好。
return "select id1, " + " id2 " + " from " + " table";
在这种情况下,无论如何,字符串连接实际上是在**编译时发生的,所以它相当于偶数的简单。
return "select id1, id2 from table";
使用new StringBuilder().append("select id1, ").append(" id2 ")....toString()
在这种情况下实际上会妨碍*性能,因为它迫使连接在执行时进行,而不是在编译时。糟糕。
如果真正的代码是通过在查询中包含值来建立SQL查询,那么这就是另一个独立的问题,即你应该使用参数化查询,在参数中而不是在SQL中指定值。
我有一篇关于String
/StringBuffer
的文章,是在StringBuilder
出现之前写的。这些原则也同样适用于`StringBuilder'。
在你发布的代码中,不会有任何好处,因为你误用了StringBuilder。你在两种情况下建立的都是同一个字符串。使用StringBuilder,你可以使用append
方法避免对字符串进行+
操作。
你应该这样使用它。
return new StringBuilder("select id1, ").append(" id2 ").append(" from ").append(" table").toString();
在Java中,String类型是一个不可变的字符序列,所以当你添加两个String时,VM会创建一个新的String值,并将两个操作数连接起来。
StringBuilder提供了一个可变的字符序列,你可以用它来连接不同的值或变量,而不需要创建新的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