2012-01-04 5 views
79

Я просто нашел некоторые SQL запросов строить, как это в моем проекте:Правильный способ использовать StringBuilder

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

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

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

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

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

Заранее благодарен!

Edit:

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

ответ

167

Цель использования StringBuilder, т. Е. Уменьшение объема памяти. Достигнуто ли это?

Нет, совсем нет. Этот код не правильно использует StringBuilder. (Я думаю, что вы исказили его, хотя, конечно, там не кавычки id2 и table?)

Обратите внимание, что цель (как правило) заключается в уменьшении модуля памяти маслобойки, а не использовали общий объем памяти, чтобы сделать жизнь немного легче в сборщике мусора.

Будет ли это иметь память равной использованию строки, как показано ниже?

Нет, это будет вызывать отток более памяти, чем просто прямой CONCAT вы цитируемый. (До /, если виртуальная машина оптимизатор не видит, что в коде явно 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 characters, который обычно слишком мал и приводит к тому, что StringBuilder должен выполнить перераспределение, чтобы сделать себя больше (IIRC, в Sun/Oracle JDK, он удваивает себя [или больше, если он знает, что ему нужно больше, чтобы удовлетворить конкретный append] каждый раз, когда он заканчивается из комнаты).

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

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

Но это только для одного выражения. Несколько StringBuilder s Для этого используются:

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

Просьба уточнить, почему это не достигнуто, чтобы помочь ему понять это. :) – Thomas

+1

Он не создает отдельный экземпляр StringBuilder для каждой конкатенации - он создает один для всего выражения. Поэтому '' select id1, «+ id2 +» из «+ table» создаст только один «StringBuilder», и будет значительно легче читать IMO. Попробуйте и декомпилируйте код, чтобы увидеть ... –

+0

@JonSkeet: Спасибо, я думал о случае с несколькими выражениями, а не о одном выражении. Исправлена. Цените заметку. –

2

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

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

В Java, тип Строка является inmutable последовательность символов, поэтому при добавлении двух строк ВМ создает новое строковое значение с обоими операндами сцепленных.

StringBuilder обеспечивает изменяемую последовательность символов, которые вы можете использовать для Concat различных значений или переменных без создания новых объектов 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

Нет, не следует. Это менее читаемо, чем использование '+', которое в любом случае будет преобразовано в один и тот же код. 'StringBuilder' полезен, когда вы не можете выполнить все конкатенации в одном выражении, но не в этом случае. –

+1

Я понимаю, что строка в вопросе размещена в качестве примера. Было бы бессмысленно строить «фиксированную» строку, подобную этой ни с StringBuilder, ни с добавлением разных фрагментов, поскольку вы могли бы просто определить ее в одной константе «select id1, id2 из таблицы» –

+0

Но даже если бы существовали непостоянные значения из переменных, он все равно будет использовать один «StringBuilder», если вы должны использовать «return» select id1, «+ foo +» something else «+ bar;» - так почему бы не сделать это? В этом вопросе нет никаких указаний на то, что что-то нужно передать 'StringBuilder'. –

34

Когда у вас уже есть все «куски» вы хотите добавить, нет смысла использовать StringBuilder вообще. Использование StringBuilderи конкатенация строк в одном вызове в соответствии с вашим примером кода еще хуже.

Это было бы лучше:

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

В этом случае конкатенации на самом деле происходит в время компиляции в любом случае, так что это эквивалентно четными простой:

return "select id1, id2 from table"; 

Использование new StringBuilder().append("select id1, ").append(" id2 ")....toString() будет фактически затруднить работу в этом случае, поскольку оно заставляет конкатенацию выполняться при времени выполнения, а не на компилировать время. К сожалению.

Если реальный код построения запроса SQL, включив в значения от в запросе, то это еще один отдельный вопрос, который является то, что вы должны использовать параметризованные запросы, указав значения в параметрах, а не в SQL.

У меня есть article on String/StringBuffer, который я написал некоторое время назад - до StringBuilder. Тем не менее, принципы применяются и к StringBuilder.

4

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

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

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

8

[[Есть некоторые хорошие ответы здесь, но я считаю, что они до сих пор не хватает немного информации. ]]

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

Смежные вопросы