Правильный способ использования StringBuilder в SQL - PullRequest
88 голосов
/ 04 января 2012

Я только что нашел такую ​​сборку SQL-запросов в своем проекте:

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

Достигает ли это StringBuilder своей цели, то есть сокращения использования памяти?

Я сомневаюсь в этом, потому чтов конструкторе используется '+' (оператор String concat).Будет ли это занимать столько же памяти, сколько при использовании String, как показано в коде ниже?s Я понял, это отличается при использовании StringBuilder.append().

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

Одинаковы ли оба оператора в использовании памяти или нет?Просьба уточнить.

Заранее спасибо!

Редактировать:

Кстати, это не мой код .Нашел это в старом проекте.Кроме того, запрос не такой маленький, как в моем примере.:)

Ответы [ 6 ]

182 голосов
/ 04 января 2012

Цель использования 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();

Обратите внимание, что я перечислил some_appropriate_size в конструкторе StringBuilder, так что он начинается с достаточной емкости для полного содержимого, которое мы собираемся добавить. Размер по умолчанию, используемый, если вы его не указали, составляет 16 символов , что обычно слишком мало и приводит к тому, что StringBuilder приходится перераспределять, чтобы сделать себя больше (IIRC, в Sun / Oracle JDK , он удваивает себя [или больше, если он знает, что ему нужно больше для удовлетворения определенного append] каждый раз, когда ему не хватает места).

Возможно, вы слышали, что при объединении строк будет использовать StringBuilder под обложками при компиляции с помощью компилятора Sun / Oracle. Это правда, он будет использовать один 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();

Так что все в порядке, хотя конструктор по умолчанию и последующее перераспределение (я) не идеальны, вероятность того, что он достаточно хорош & mdash; и конкатенация является много более читабельным.

Но это только для одного выражения. Для этого используются несколько 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;

... что довольно уродливо.

Важно помнить, однако, что во всех, кроме очень немногих случаях это не имеет значения , и предпочтение отдается удобочитаемости (что повышает удобство обслуживания), если исключить конкретную проблему производительности.

38 голосов
/ 04 января 2012

Когда у вас уже есть все «кусочки», которые вы хотите добавить, нет смысла использовать 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 таким же образом.

10 голосов
/ 25 июля 2014

[[Здесь есть несколько хороших ответов, но я считаю, что им все еще не хватает информации.]]

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

Наконец, как указывает @TJrowder, вы всегда должны делать предположения о размере StringBuilder.Это позволит сэкономить на количестве char[] объектов, созданных при увеличении размера внутреннего буфера.

4 голосов
/ 04 января 2012

Вы правы, предполагая, что цель использования строителя строк не достигнута, по крайней мере, не в полной мере.

Однако, когда компилятор видит выражение "select id1, " + " id2 " + " from " + " table", он генерирует код, который фактически создает StringBuilder за кадром и добавляет к нему, так что конечный результат не так уж плох.

Но, конечно, любой, кто смотрит на этот код, неизбежно думает, что он немного отсталый.

2 голосов
/ 04 января 2012

В размещенном вами коде не будет никаких преимуществ, так как вы неправильно используете StringBuilder.Вы строите одну и ту же строку в обоих случаях.Используя StringBuilder, вы можете избежать операции + со строками, используя метод append.Вы должны использовать это следующим образом:

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

В Java тип String является неизменяемой последовательностью символов, поэтому при добавлении двух строк строка виртуальная машина создает новое значение 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

1 голос
/ 15 сентября 2013

Вы также можете использовать MessageFormat тоже

...