Правильный способ использования StringBuilder

Я только что нашел в моем проекте сборку sql, как это в моем проекте:

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

Достигает ли этого StringBuilder своей цели, т.е. уменьшает использование памяти?

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

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

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

Спасибо заранее!

Edit:

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

Ответ 1

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

Итак, это нормально, хотя конструктор по умолчанию и последующее перераспределение не являются идеальными, вероятность того, что он достаточно хорош — и конкатенация становится более читаемой.

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

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

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

Ответ 2

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

Ответ 3

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

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, я создаю строку из нескольких вызовов методов. Затем я могу создать методы, которые принимают аргумент 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[] объектов, созданных при увеличении размера внутреннего буфера.

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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