Сколько байтов в памяти занимает строка hello

Обновлено: 06.07.2024

Подскажите, пожалуйста, каким образом выделяется память в Delphi под переменную типа String?
Ведь максимальный объём переменной может достигать 2Гб (это я с Интерента взял, адрес сайта приводить не буду).

Добавлено через 13 минут
И надо ли в таком случае ограничивать размер переменной до необходимых размеров (допустим, String[300])?

__________________
Помощь в написании контрольных, курсовых и дипломных работ здесь

Для трех типов данных Объём памяти, занимаемый экземпляром СД
Задание 1. Для трех типов данных (см. вариант индивидуального задания) определить: 1.1. Объём.

Объём памяти, занимаемый объектом TImage
Добрый день. Объясните ламеру, как узнать объём памяти, занимаемый объектом TImage с загруженной в.

Возрастает занимаемый объем памяти при перерисовке объекта
Добрый день. При нажатии на кнопку Q1 запускается анимация контакта. При повторном нажатии контакт.

Acronis true image clone hdd увеличился занимаемый объем
Добрый день! Сделал полное клонирование жесткого диска с 500Гб на 160Гб диск. В результате.

Выделяется столько, сколько надо на текущий момент. При изменении перевыделяется заново. AnyAdd - это некоторая величина, выбираемая менеджером памяти Delphi. Эти дополнительные байты видимо расположены в старших адресах относительно терминального нуля. Я не встречал в инете какой-либо инфы об их назначении. Чем длиннее строка, тем значение AnyAdd может быть бOльшим.
И надо ли в таком случае ограничивать размер переменной до необходимых размеров (допустим, String[300])? Запись: var Str1 : String[300]; - недопустима. Т. к. таким образом объявляются статические строки типа ShortString. Для такой строки максмальное кол-во символов = 255. Первй байт (это байт с индексом 0) в такой строке Str1[0] содержит целое число, равное количеству значимых символов в строке. Действительно вроде так, но про размер выделенной памяти в разделе хелпа про строки не говорится. Это поле, по-моему, относится к GetMem и менеджеру памяти, а не к строкам. Все перечисленные поля - данные строки и метаданные - это целостный объект. Таким же образом устроены динамические массивы в Delphi. Действительно вроде так, но про размер выделенной памяти в разделе хелпа про строки не говорится. Очень даже говорится. В Delphi 7: Содержание - Delphi Language Guide - Data types, . - String types - Long strings:

A long-string variable is a pointer occupying four bytes of memory. When the variable is empty--that is, when it contains a zero-length string--the pointer is nil and the string uses no additional storage. When the variable is nonempty, it points a dynamically allocated block of memory that contains the string value. The eight bytes before the location contain a 32-bit length indicator and a 32-bit reference count. This memory is allocated on the heap, but its management is entirely automatic and requires no user code.

Because long-string variables are pointers, two or more of them can reference the same value without consuming additional memory. The compiler exploits this to conserve resources and execute assignments faster. Whenever a long-string variable is destroyed or assigned a new value, the reference count of the old string (the variable's previous value) is decremented and the reference count of the new value (if there is one) is incremented; if the reference count of a string reaches zero, its memory is deallocated. This process is called reference-counting. When indexing is used to change the value of a single character in a string, a copy of the string is made if--but only if--its reference count is greater than one. This is called copy-on-write semantics.

Итак, начнем с представления строк в памяти

  1. Длина строки ограничена неким числом в отличие от PWSZ, где длина строки ограничена наличием свободной памяти.
  2. BSTR строка всегда указывает на первый символ в буфере. PWSZ может указывать на любой символ в буфере.
  3. У BSTR всегда в конце находится null символ, так же как и у PWSZ, но в отличие от последнего он является валидным символом и может встречаться в строке где угодно.
  4. За счет наличия null-символа в конце BSTR совместим с PWSZ, но не наоборот.

Использование такой реализации имеет ряд преимуществ: длину строки не нужно пересчитывать она хранится в заголовке, строка может содержать null-символы, где угодно, и самое главное адрес строки(pinned) можно без проблем передавать в неуправляемой код там, где ожидается WCHAR*.

Сколько памяти занимает объект строкового типа?

Мне встречались статьи где было написано, что размер строкового объекта равен size = 20 + (length/2)*4, однако эта формула не совсем правильная.
Начнем с того, что строка является ссылочным типом, поэтому первые 4 байта содержат SyncBlockIndex, а вторые 4 байта содержат указатель на тип.

Размер строки = 4 + 4 + .

Как было выше сказано, в буфере хранится длина строки — это поле типа int, значит еще 4 байта.

Размер строки = 4 + 4 + 4 + .

Для того, чтобы быстро передать строку в неуправляемый код (без копирования) в конце каждой строки стоит null-терминированный символ, который занимает 2 байта, значит

Размер строки = 4 + 4 + 4 + 2 + .

Осталось вспомнить, что каждый символ в строке находится в UTF -16 кодировке значит, занимает так же 2 байта, следовательно

Размер строки = 4 + 4 + 4 + 2 + 2 * length = 14 + 2 * length

Учтем еще один нюанс, и мы у цели. А именно менеджер памяти в CLR выделяет память кратной 4 байтам (4, 8, 12, 16, 20, 24, . ), то есть если длина строки суммарно будет занимать 34 байта, то выделено будет 36 байта. Нам необходимо округлить наше значение к ближайшему большему кратному четырем числу, для этого необходимо:

Размер строки = 4 * ((14 + 2 * length + 3) / 4) (деление естественно целочисленное)

Особенности строк

Итак, мы рассмотрели, как представляются строки, и сколько на самом деле они занимают места в памяти. Теперь давайте погорим об их особенностях.

  1. Они являются ссылочными типами.
  2. Они неизменяемы. Однажды, создав строку, мы больше не можем ее изменить (честным способом). Каждый вызов метода этого класса возвращает новую строку, а предыдущая строка становится добычей для сборщика мусора.
  3. Они переопределяют метод Object.Equals, в результате чего он сравнивает не значения ссылок, а значения символов в строках.

Строки — ссылочные типы

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

Строки — неизменяемы

  1. Ссылка на массив символов char;
  2. Индекс первого символа строки в массиве char (смещение он начала);
  3. Количество символов в строке;
  4. Посчитанный хэш-код, после первого вызова метода hashCode();

Реализация метода String.substring() в Java:


Однако, согласно принципу ЛДНБ (ланчей даром не бывает), о котором так часто говорит Эрик Липперт не все так хорошо. Если исходная строка будет достаточно большой, а вырезаемая подстрока в пару символов, то весь массив символов первоначальной строки будет висеть в памяти пока есть ссылка на подстроку или, если вы сериализуете полученную подстроку стандартными средствами и передаете её по сети, то будет сериализован весь оригинальный массив и количество передаваемых байтов по сети будет большим. Поэтому в таком случае вместо кода

s = ss.substring(3)

можно использовать код

s = new String(ss.substring(3)),

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

Как оказалось в последней версии Java реализация строкового типа изменилась. xonix подсказал об этом. Теперь в классе нет полей offset и length, и появился новый hash32 (с другим алгоритмом хеширования). Это означает, что строки перестали быть персистентными. Теперь метод String.substring каждый раз будет создаваться новую строку.

Строки переопределяют Object.Equals

Класс String переопределяет метод Object.Equals, в результате чего сравнение происходит не по ссылке, а по значению. Я думаю, разработчики благодарны создателям класса String за то, что они переопределили оператор ==, так как код, использующий == для сравнения строк, выглядит более изящно, нежели вызов метода.


Кстати, в Java оператор == сравнивает по ссылке, а для того чтобы сравнить строки посимвольно необходимо использовать метод string.equals().

Интернирование строк

Ну, и на последок поговорим об интернировании строк.
Рассмотрим простой пример, код который переворачивает строку.


Очевидно, данный код не с компилируется. Компилятор будет ругаться на эти строки, потому что мы пытаемся изменить содержимое строки. Действительно, любой метод класса String возвращает новый экземпляр строки, вместо того чтобы изменять свое содержимое.

На самом деле строку можно изменить, но для этого придется прибегнуть к unsafe коду. Рассмотрим пример:


После выполнения этого кода, как и ожидалось, в строке будет записано elbatummi era sgnirtS.
Тот факт, что строки являются все-таки изменяемыми, приводит к одному очень интересному казусу. Связан он с интернированием строк.

Интернирование строк — это механизм, при котором одинаковые литералы представляют собой один объект в памяти.

Если не вникать глубоко в подробности, то смысл интернирования строк заключается в следующем: в рамках процесса (именно процесса, а не домена приложения) существует одна внутренняя хеш-таблица, ключами которой являются строки, а значениями – ссылки на них. Во время JIT-компиляции литеральные строки последовательно заносятся в таблицу (каждая строка в таблице встречается только один раз). На этапе выполнения ссылки на литеральные строки присваиваются из этой таблицы. Можно поместить строку во внутреннюю таблицу во время выполнения с помощью метода String.Intern. Также можно проверить, содержится ли строка во внутренней таблице с помощью метода String.IsInterned.


Важно отметить, что интернируются по умолчанию только строковые литералы. Поскольку для реализации интернирования используется внутренняя хеш-таблица, то во время JIT компиляции происходит поиск по ней, что занимает время, поэтому если бы интернировались все строки, то это свело бы на нет всю оптимизацию. Во время компиляции в IL код, компилятор конкатенирует все литеральные строки, так как нет в необходимости содержать их по частям, поэтому 2 — ое равенство возвращает true. Так вот, в чем заключается казус. Рассмотрим следующий код:


Кажется, что здесь все очевидно и, что такой код должен распечатать Strings are immutable. Однако, нет! Код напечатает elbatummi era sgnirtS. Дело именно в интернировании, изменяя строку s, мы меняем ее содержимое, а так как она является литералом, то интернируется и представляется одним экземпляром строки.

От интернирования строк можно отказаться, если применить специальный атрибут CompilationRelaxationsAttribute к сборке. Атрибут CompilationRelaxationsAttribute контролирует точность кода, создаваемого JIT-компилятором среды CLR. Конструктор данного атрибута принимает перечисление CompilationRelaxations в состав, которого на текущий момент входит только CompilationRelaxations.NoStringInterning — что помечает сборку как не требующую интернирования.

А что если без unsafe?


Таким образом, используя следующий код, можно изменить содержимое строки, даже не прибегая к использованию unsafe коду.


Этот код как уже ожидалось, напечатает elbatummi era sgnirtS.

Особенности производительности

У интернирования есть отрицательный побочный эффект. Дело в том, что ссылка на интернированный объект String, которую хранит CLR, может сохраняться и после завершения работы приложения и даже домена приложения. Поэтому большие литеральные строки использовать не стоит или же, если это необходимо стоит отключить интернирование, применив атрибут CompilationRelaxations к сборке.

Данные, обрабатываемые вычислительной машиной, можно разделить на 4 группы:

  • целочисленные;
  • вещественные.
  • символьные;
  • логические;
Целочисленные данные

Беззнаковые целые числа представляются в виде последовательности битов в диапазоне от 0 до 2 n -1, где n- количество занимаемых битов.


Знаковые целые числа представляются в диапазоне -2 n-1 … +2 n-1 -1. При этом старший бит данного отводится под знак числа (0 соответствует положительному числу, 1 – отрицательному).

Вещественные данные

Вещественные данные могут быть 4, 8 или 10-байтными и обрабатываются математическим сопроцессором.

Логические данные

Логические данные представляют собой бит информации и могут записываться в виде последовательности битов. Каждый бит может принимать значение 0 (ЛОЖЬ) или 1 (ИСТИНА). Логические данные могут начинаться с любой позиции в байте.

Символьные данные

Символьные данные задаются в кодах и имеют длину, как правило, 1 байт (для кодировки ASCII) или 2 байта (для кодировки Unicode) .

Числа в двоично-десятичном формате

В двоично-десятичном коде представляются беззнаковые целые числа, кодирующие цифры от 0 до 9. Числа в двоично-десятичном формате могут использоваться в одном из двух видов:

В неупакованном виде в каждом байте хранится одна цифра, размещенная в младшей половине байта (биты 3…0).

Упакованный вид допускает хранение двух десятичных цифр в одном байте, причем старшая половина байта отводится под старший разряд.

Числовые константы

Числовые константы используются для обозначения арифметических операндов и адресов памяти. Для числовых констант в Ассемблере могут использоваться следующие числовые форматы.

Десятичный формат – допускает использование десятичных цифр от 0 до 9 и обозначается последней буквой d, которую можно не указывать, например, 125 или 125d. Ассемблер сам преобразует значения в десятичном формате в объектный шестнадцатеричный код и записывает байты в обратной последовательности для реализации прямой адресации.

Шестнадцатеричный формат – допускает использование шестнадцатеричных цифр от 0 до F и обозначается последней буквой h, например 7Dh. Так как ассемблер полагает, что с буквы начинаются идентификаторы, то первым символом шестнадцатеричной константы должна быть цифра от 0 до 9. Например, 0Eh.

Двоичный формат – допускает использование цифр 0 и 1 и обозначается последней буквой b. Двоичный формат обычно используется для более четкого представления битовых значений в логических командах (AND, OR, XOR).

Восьмеричный формат – допускает использование цифр от 0 до 7 и обозначается последней буквой q или o, например, 253q.

Массивы и цепочки

Массивом называется последовательный набор однотипных данных, именованный одним идентификатором.

Примеры инициализации цепочек

Каждая из записей выделяет десять последовательных 4-байтных ячеек памяти и записывает в них значения 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

Идентификатор M1 определяет смещение начала этой области в сегменте данных . DATA .

Для инициализации всех элементов массива одинаковыми значениями используется оператор DUP :

Идентификатор Тип Размер DUP (Значение)

описывает массив a из 20 элементов, начальные значения которых равны 0.

Если необходимо выделить память, но не инициализировать ее, в качестве поля Значение используется знак ?. Например,

Символьные строки

Символьные строки представляют собой набор символов для вывода на экран. Содержимое строки отмечается

Символьная строка определяется только директивой DB , в которой указывается более одного символа в последовательности слева направо.

По крайней мере у вас есть три общих способа измерить длину текста, если вы используете платформу Java:

  1. количество знаков char в коде
  2. количество символов (characters) или кодовых единиц
  3. число байтов

Подсчет знаков char

В платформе Java используется Unicode Standard для определения символов. Unicode Standard определяет и фиксирует для каждого символа значение, состоящее из 16 битов, в пределе от U+0000 до U+FFFF. Префикс U+ означает допустимое значение в Юникоде как шестнадцатеричное число. В языке Java стандарт фиксированного размера символов удобно преобразуется в тип char . Таким образом значение char может быть представлено любым символом в 16-битном Юникоде.

Большинство программистов знакомы с методом length . Код, приведенный ниже, считает количество знаков char в примере строки. Обратите внимание, что пример объекта String содержит несколько простых символов и несколько символов, определенных в \u нотации языка Java. \u нотация определеяет шестнадцатеричное число и является аналогом нотации U+ , используемой Unicode Standard.

Метод length считает количество знаков char в объекте String . Вот что выведет этот код:

Подсчет символов

Когда Unicode версии 4.0 определяет важные новые символы выше U+FFFF, 16-битный тип char не может более представлять все символы. Начиная с Java 2 Platform, Standard Edition 5.0 (J2SE 5.0), платформа Java поддерживает новые символы Юникода - пары 16-битных знаков char , которые называются суррогатными парами ( surrogate pair ). Два знака char действуют как суррогатное представление символов Юникода в диапазоне от U+10000 до U+10FFFF. Символы в таком новом диапазоне называются дополнительные символы ( supplementary characters ).

Метод length не может считать дополнительные символы, так как он считает только знаки char . К счастью в J2SE 5.0 API есть новый метод String : codePointCount(int beginIndex, int endIndex) . Этот метод показывает, сколько единиц Юникода (символов) между двумя индексами. Значения индексов ссылаются на код, обозначающий местоположение знака char . Значение выражения endIndex - beginIndex такое же как и значение, полученное с помощью метода length . Но это не всегда равно значению, возвращаемому методом codePointCount . Если ваш текст содержит суррогатные пары, вычисляемая длина сильно изменится. Суррогатная пара определяет код одного символа, который может состоять из одного или двух знаков char .

Чтобы узнать, сколько символов Юникода в строке, используйте метод codePointCount :

Этот пример выведет следующее:

Переменная testString содержит два интересных символа: японский иероглиф, обозначающий "учение", и буква готского алфавита А ( GOTHIC LETTER AHSA ). Японский иероглиф в Юникоде имеет значение U+5B66 и такой же номер знака char в шестнадцатеричной системе \u5B66. Значение готской буквы - U+10330. В UTF-16 готская буква состоит из суррогатной пары \uD800\uDF30. Пара представляет один целый символ в Юникоде, таким образом число символов в строке равно 6, а не 7.

Подсчет байтов

Сколько байт в строке String ? Ответ зависит от использованной кодировки. Одной из наиболее распространенных причин спрашивать "сколько байт?" является желание убедится, что вы удовлетворили ограничением на длину строки в базе данных. Метод getBytes преобразует символы Юникода в байтовую кодировку (в кодировку, работающую не с символами, а байтами) и возвращает количество байт: byte[] . Одной из байтовых кодировок является UTF-8 . Это самая распространенная байтовая кодировка, потому что может точно представлять символы Юникода.

Далее представлен код, который преобразует текст в массив байтовых значений:

Рисунок 1. Строки имеют различную длину, зависящую от того, что вы считаете.

В заключение

Даже используя дополнительные символы, вы никогда не увидите разницу между возвращаемыми значениями метода length и метода codePointCount . Однако, когда вы используете символы выше U+FFFF, вам пригодится умение определять длину различными способами. Если вы будете посылать свои продукты в Японию или Китай, то наверняка попадете в ситуацию, когда методы length и codePointCount вернут различные значения. Базы данных и некоторые форматы публикаций поощряют использование в качестве кодировки UTF-8. Но даже в этом случае измерение длины текста может дать различные результаты. В зависимости от того, как вы будете использовать длину, у вас есть различные способы ее измерить.

Дополнительная информация

Используйте эти ресурсы, чтобы найти информацию по теме данного технического совета:

Читайте также: