Строка vs char [] - программирование
Подтвердить что ты не робот

Строка vs char []

У меня есть несколько слайдов от IBM: "От Java-кода до Java-кучи: понимание использования памяти вашего приложения" , в котором говорится, когда мы используем String вместо char[], существует

Максимальные накладные расходы будут 24: 1 для одного символа!

но я не могу понять, какие здесь накладные расходы. Кто-нибудь может помочь?

Источник:

enter image description here

4b9b3361

Ответ 1

Эта цифра относится к JDK 6-32 бит.

JDK 6

В строках мира pre-Java-7, которые были реализованы как указатель на область массива char[]:

// "8 (4)" reads "8 bytes for x64, 4 bytes for x32"

class String{      //8 (4) house keeping + 8 (4) class pointer
    char[] buf;    //12 (8) bytes + 2 bytes per char -> 24 (16) aligned
    int offset;    //4 bytes                     -> three int
    int length;    //4 bytes                     -> fields align to
    int hash;      //4 bytes                     -> 16 (12) bytes
}

Итак, я подсчитал:

36 bytes per new String("a") for JDK 6 x32  <-- the overhead from the article
56 bytes per new String("a") for JDK 6 x64.


JDK 7

Просто для сравнения, в JDK 7+ String - это класс, который содержит только буфер char[] и поле hash.

class String{      //8 (4) + 8 (4) bytes             -> 16 (8)  aligned
    char[] buf;    //12 (8) bytes + 2 bytes per char -> 24 (16) aligned
    int hash;      //4 bytes                         -> 8  (4)  aligned
}

Итак, это:

28 bytes per String for JDK 7 x32 
48 bytes per String for JDK 7 x64.

UPDATE

Для отношения 3.75:1 см. объяснение @Andrey ниже. Эта пропорция падает до 1, так как длина строки растет.

Полезные ссылки:

Ответ 2

В JVM символьная переменная сохраняется в одном 16-разрядном распределении памяти, и изменения этой переменной Java переписывают ту же ячейку памяти. Это делает создание или обновление символьных переменных очень быстрым и дешевым, но увеличивает JVM накладные расходы по сравнению со статическим распределением, используемым в строках.

JVM хранит строки Java в пространстве памяти с переменным размером (по существу, массив), который является точно таким же размером (плюс 1 для символа завершения строки) строки, когда объект String создан или сначала назначен стоимость. Таким образом, объект с начальным значением "HELP!" будет выделено 96 бит памяти (6 символов, каждый из 16 бит). Это значение считается неизменным, позволяя JVM встроить ссылки на эту переменную, делая статические назначения строк очень быстрыми и очень компактными, а также очень эффективными с точки зрения JVM.

Ссылка

Ответ 3

Я попробую объяснить номера, упомянутые в исходной статье.

В статье описываются метаданные объекта, обычно состоящие из: класса, флагов и блокировки.

Класс и блокировка хранятся в заголовке объекта и занимают 8 байтов на 32-битной виртуальной машине. Я не нашел ни одной информации о реализации JVM, которая имеет информацию о флагах в заголовке объекта. Возможно, это хранится где-то снаружи (например, сборщиком мусора для подсчета ссылок на объект и т.д.).

Итак, допустим, что в статье рассказывается о некотором x32 AbstractJVM, который использует 12 байт памяти для хранения метаинформации об объекте.

Тогда для char[] имеем:

  • 12 байтов метаинформации (8 байтов на x32 JDK 6, 16 байт на x64 JDK)
  • 4 байта для размера массива
  • 2 байта для каждого сохраненного символа
  • 2 байта выравнивания, если число символов нечетное (на x64 JDK: 2 * (4 - (length + 2) % 4))

Для java.lang.String имеем:

  • 12 байтов метаинформации (8 байтов на x32 JDK6, 16 байт на x64 JDK6)
  • 16 байт для строковых полей (это для JDK6, 8 байт для JDK7)
  • памяти, необходимой для хранения char [], как описано выше

Итак, пусть подсчитывает, сколько памяти требуется для хранения "MyString" как объекта String:

12 + 16 + (12 + 4 + 2 * "MyString".length + 2 * ("MyString".length % 2)) = 60 bytes.

С другой стороны мы знаем, что для хранения только данных (без информации о типе данных, длине или чем-то еще) нам нужно:

2 * "MyString".length = 16 bytes

Накладные расходы 60 / 16 = 3.75

Аналогично для одиночного символьного массива мы получаем "максимальную служебную нагрузку":

12 + 16 + (12 + 4 + 2 * "a".length + 2 * ("a".length % 2)) = 48 bytes
2 * "a".length = 2 bytes
48 / 2 = 24

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

Ответ 4

Я прочитал из старого ответа stackoverflow, который не смог его получить. В Oracle JDK строка содержит четыре поля уровня экземпляра:

A character array
An integral offset
An integral character count
An integral hash value

Это означает, что каждая строка вводит дополнительную ссылку на объект (сама строка) и три целых числа в дополнение к самому массиву символов. (Смещение и количество символов есть, чтобы разрешить совместное использование массива символов среди экземпляров String, созданных с помощью методов String # substring(), выбор дизайна, который некоторые другие разработчики библиотеки Java избегали.) Помимо дополнительных затрат на хранение, есть также один более высокий уровень доступа, не говоря уже о проверке границ, с которой String защищает свой массив символов.

Если вы можете избавиться от выделения и потребления только основного массива символов, там будет сохранено место. Конечно, это не так уж идиоматично для Java; разумные комментарии были бы оправданы, чтобы оправдать выбор, предпочтительно с упоминанием доказательств из-за того, что он профилировал разницу.