Эффективно добавлять последние символы в StringBuilder - PullRequest
3 голосов
/ 02 ноября 2019

Примечание: этот вопрос о Java> = 9, который представил "компактные строки"


Допустим, я добавляю неизвестное количество строк (или символов) кStringBuilder и в какой-то момент определите, что я добавляю последнюю строку.

Как это можно сделать эффективно?

Фон

Если емкость строителя строк равнанедостаточно большой, он всегда будет увеличиваться до max(oldCap + str.lenght(), oldCap * 2 + 2). Так что, если вам не повезло и емкости недостаточно для последней строки, это излишне удвоит пропускную способность, например:

StringBuilder sb = new StringBuilder(4000);
sb.append("aaa..."); // 4000 * "a"
// Last string:
sb.append("b"); // Unnecessarily increases capacity from 4000 to 8002
return sb.toString();

StringBuilder предлагает методы capacity(), length() и getChars(...), однако ручное создание char[] и последующее создание строки будет неэффективным, потому что:

  • Из-за "компактных строк" построитель строк должен преобразовывать свои байты в символы
  • При вызове одного из конструкторов String символы должны быть снова сжаты до байтов

Другой вариант - проверить capacity() и, если необходимо, создать new StringBuilder(sb.length() + str.length()), а затем добавить sbи str:

StringBuilder sb = new StringBuilder(4000);
sb.append("aaa..."); // 4000 * "a"

String str = "b";
if (sb.capacity() - sb.length() < str.length()) {
    return new StringBuilder(sb.length() + str.length())
        .append(sb)
        .append(str)
        .toString();
}
else {
    return sb.append(str).toString();
}

Единственным недостатком является то, что если существующий построитель строк или новая строка не являются латинскими 1 (2 байта на символ), то вновь созданный построитель строк должен быть «раздут». "от 1 байта на символ (латинская 1) до 2 байтов на символ.

1 Ответ

0 голосов
/ 03 ноября 2019

Вы описываете разные проблемы IMO, но ни одна из них не является "реальной" проблемой.

Во-первых, это тот факт, что StringBuilder выделяет слишком много места - это редко (если вообще когда-либо)проблема на практике. Подумайте о any List/Set/Map - они делают одно и то же, могут выделять слишком много, но когда вы удаляете элемент, они не уменьшают внутреннее хранилище. У них есть метод для этого;но так же StringBuilder:

 trimToSize

Из-за «компактных строк» ​​построитель строк должен преобразовывать свои байты в символы.

StringBuilder знает что он хранит через поле coder в AbstractStringBuilder, которое он расширяет. С компактными строками String теперь хранит свои данные в byte[] (он тоже имеет coder), поэтому я не понимаю, где это преобразование из byte[] в char[] должно происходить. StringBuilder::toString определяется как:

public String toString() {
    // Create a copy, don't share the array
    return isLatin1() ? StringLatin1.newString(value, 0, count)
                      : StringUTF16.newString(value, 0, count);
}

Обратите внимание на проверку isLatin1 - StringBuilder знает, какой тип данных у него внутри;таким образом, невозможно преобразование, когда это возможно.

Я предполагаю, что следующим образом:

При вызове одного из конструкторов String символы должны быть снова сжаты до байтов

Вы имеете в виду:

char [] some = ...
String s = new String(some);

Я не знаю, почему вы снова используете , но, возможно, я что-то упустил. Просто обратите внимание, что это преобразование из char[] в byte[] действительно должно произойти, но это довольно тривиально (последние 8 бит должны быть пустыми), и как только один char не удовлетворяет предварительному условию,все обращение выручено. Таким образом, вы либо сохраняете все символов в LATIN1, либо нет.

...