在SQL中使用StringBuilder的正确方法

Vaa*_*ndu 88 java string stringbuilder

我刚在我的项目中发现了一些这样的sql查询构建:

return (new StringBuilder("select id1, " + " id2 " + " from " + " table")).toString();
Run Code Online (Sandbox Code Playgroud)

这是否StringBuilder达到了目标,即减少内存使用量?

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

return "select id1, " + " id2 " + " from " + " table";
Run Code Online (Sandbox Code Playgroud)

这两个语句在内存使用方面是否相同?请澄清.

提前致谢!

编辑:

顺便说一下,这不是我的代码.在一个旧项目中找到它.此外,查询不是我的示例中的查询.:)

T.J*_*der 182

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

一点都不.该代码未StringBuilder正确使用.(我认为你错误引用了它;肯定没有引号id2table?)

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

将内存等同于使用如下所示的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();
Run Code Online (Sandbox Code Playgroud)

请注意,我已some_appropriate_sizeStringBuilder构造函数中列出,因此它开始时具有足够的容量来容纳我们要追加的完整内容.如果不指定一个字符,则使用的默认大小为16个字符,这通常太小而导致StringBuilder必须进行重新分配以使其自身更大(IIRC,在Sun/Oracle JDK中,它会使自身翻倍[或更多,如果它知道append每次用完房间时需要更多来满足特定的要求.

您可能听说过,如果使用Sun/Oracle编译器进行编译,字符串连接使用StringBuilder隐藏.这是事实,它将使用一个StringBuilder用于整体表达.但它将使用默认构造函数,这意味着在大多数情况下,它将不得不进行重新分配.不过,它更容易阅读.请注意,对于一系列连接,情况并非如此.例如,这使用一个:StringBuilder

return "prefix " + variable1 + " middle " + variable2 + " end";
Run Code Online (Sandbox Code Playgroud)

它大致翻译为:

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();
Run Code Online (Sandbox Code Playgroud)

所以没关系,虽然默认的构造函数和随后的重新分配(S)不理想,赔率是它不够好-以及级联是一个很大的可读性.

但这仅适用于单一表达.多个StringBuilders用于此:

String s;
s = "prefix ";
s += variable1;
s += " middle ";
s += variable2;
s += " end";
return s;
Run Code Online (Sandbox Code Playgroud)

最终变成这样的事情:

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;
Run Code Online (Sandbox Code Playgroud)

......这很丑陋.

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


Jon*_*eet 38

当你已经拥有了想要追加的所有"碎片"时,根本没有必要使用它StringBuilder.根据您的示例代码在同一个调用中使用StringBuilder 字符串连接更糟糕.

这会更好:

return "select id1, " + " id2 " + " from " + " table";
Run Code Online (Sandbox Code Playgroud)

在这种情况下,字符串连接实际上是在编译时发生的,因此它等同于更简单的:

return "select id1, id2 from table";
Run Code Online (Sandbox Code Playgroud)

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

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

我有一篇关于String/StringBuffer我之前写过的文章 - 之前有过StringBuilder.这些原则StringBuilder同样适用于此.


Gra*_*ray 10

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

return (new StringBuilder("select id1, " + " id2 " + " from " + " table"))
     .toString();
Run Code Online (Sandbox Code Playgroud)

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

return new StringBuilder("select id1,  id2  from  table").toString();
Run Code Online (Sandbox Code Playgroud)

在这种情况下,显然,使用没有意义StringBuilder.你不妨这样做:

// the compiler combines these constant strings
return "select id1, " + " id2 " + " from " + " table";
Run Code Online (Sandbox Code Playgroud)

但是,即使您附加任何字段或其他非常量,编译器也会使用内部 StringBuilder - 您无需定义一个:

// an internal StringBuilder is used here
return "select id1, " + fieldName + " from " + tableName;
Run Code Online (Sandbox Code Playgroud)

在封面下,这变成了大致相当于的代码:

StringBuilder sb = new StringBuilder("select id1, ");
sb.append(fieldName).append(" from ").append(tableName);
return sb.toString();
Run Code Online (Sandbox Code Playgroud)

真的是你需要StringBuilder 直接使用的唯一时间是你有条件代码.例如,看起来如下的代码迫切需要StringBuilder:

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

+在第一行使用一个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();
Run Code Online (Sandbox Code Playgroud)

我使用a的另一个时间是我StringBuilder从许多方法调用构建一个字符串.然后我可以创建带StringBuilder参数的方法:

private void addWhere(StringBuilder sb) {
   if (where != null) {
      sb.append(' ').append(where);
   }
}
Run Code Online (Sandbox Code Playgroud)

当您使用a时StringBuilder,您应该同时注意任何用法+:

sb.append("select " + fieldName);
Run Code Online (Sandbox Code Playgroud)

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

sb.append("select ").append(fieldName);
Run Code Online (Sandbox Code Playgroud)

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