在SQL中使用StringBuilder的正确方法

时间:2012-01-04 11:03:02

标签: java string stringbuilder

我刚刚在我的项目中找到了一些这样的SQL查询构建:

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

StringBuilder是否实现了目标,即减少内存使用量?

我对此表示怀疑,因为在构造函数中使用了'+'(String concat运算符)。这会占用与使用String相同的内存量,如下面的代码吗?我明白,使用StringBuilder.append()时它会有所不同。

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

两个语句的内存使用量是否相等?请澄清。

提前致谢!

编辑:

BTW,这不是我的代码。在一个旧项目中找到它。此外,查询不是我的示例中的查询。 :)

6 个答案:

答案 0 :(得分:182)

  

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

不,一点也不。该代码未正确使用StringBuilder。 (我认为你错误引用了它;当然id2table周围没有引号?)

请注意,目标(通常)是减少内存 churn 而不是使用的总内存,以使垃圾收集器的生活更轻松。

  

将内存等同于使用如下所示的字符串吗?

不,它会导致更多内存流失,而不仅仅是你引用的直接连接。 (直到/除非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();

请注意,我在some_appropriate_size构造函数中列出了StringBuilder,因此它开始时有足够的容量来容纳我们要添加的完整内容。如果不指定一个,则使用的默认大小为16 characters,这通常太小,导致StringBuilder不得不重新分配以使自己更大(IIRC,在Sun / Oracle JDK中,它会自我加倍[或者更多,如果它知道每次用完房间时它需要更多来满足特定的append]。

如果使用Sun / Oracle编译器编译,您可能听说字符串连接使用StringBuilder。这是事实,它将使用一个StringBuilder作为整体表达式。但它将使用默认构造函数,这意味着在大多数情况下,它将不得不进行重新分配。不过,它更容易阅读。请注意,这是系列连接的 true。例如,这使用一个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();

所以没关系,虽然默认的构造函数和后续的重新分配是不理想的,但它的可能性足够大 - 并且连接是 lot 更具可读性。

但这仅适用于单个表达式。多个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;

......这很难看。

重要的是要记住,除了极少数情况外,无关紧要并且具有可读性(增强可维护性)是首选,除非存在特定的性能问题。

答案 1 :(得分:38)

如果您已经拥有了想要追加的所有“部分”,那么使用StringBuilder毫无意义。根据您的示例代码在同一个调用中使用StringBuilder 字符串连接更糟糕。

这会更好:

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

在这种情况下,字符串连接实际上发生在编译时,所以它相当于偶数更简单:

return "select id1, id2 from table";

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

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

我有一段article on String / StringBuffer我刚才写过 - StringBuilder出现之前。这些原则同样适用于StringBuilder

答案 2 :(得分:10)

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

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";

但是,即使您附加任何字段或其他非常量,编译器也会使用 internal 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的另一次是当我从多个方法调用构建字符串时。然后我可以创建采用StringBuilder参数的方法:

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

当您使用StringBuilder时,您应该同时注意+的任何使用情况:

sb.append("select " + fieldName);

+将导致另一个内部StringBuilder被创建。这当然应该是:

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

最后,正如@ T.J.rowder所指出的那样,你应该总是猜测StringBuilder的大小。这将节省增加内部缓冲区大小时创建的char[]个对象的数量。

答案 3 :(得分:4)

你猜错了使用字符串构建器的目的是不正确的,至少不是完全的。

然而,当编译器看到表达式"select id1, " + " id2 " + " from " + " table"时,它会发出实际在幕后创建StringBuilder并附加到其上的代码,因此最终结果并不是那么糟糕。

但是当然,任何看过该代码的人都会认为它有点迟钝。

答案 4 :(得分:2)

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

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

在Java中,String类型是一个不可变的字符序列,因此当您添加两个字符串时,VM会创建一个新的String值,并将两个操作数连接起来。

StringBuilder提供了一个可变的字符序列,您可以使用它来连接不同的值或变量,而无需创建新的String对象,因此它有时比使用字符串更有效

这提供了一些有用的功能,例如更改在另一个方法中作为参数传递的char序列的内容,这是使用字符串无法做到的。

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

的更多信息

答案 5 :(得分:1)

您也可以使用MessageFormat