Как значение типа Word влияет на хранение длины строки в байтах

Далее необходимо выбрать конкретный тип данных из представленных в классе. Многие типы данных MySQL позволяют хранить данные одного и тот же вида, но с разным диапазоном значений, точностью или требуемым физическим пространством (на диске или в памяти). Некоторые типы обладают специальным поведением или свойствами.

Например, в столбцах DATETIME и TIMESTAMP можно хранить один и тот же тип данных: дату и время, с точностью до секунды. Однако тип TIMESTAMP требует вдвое меньше места, позволяет работать с часовыми поясами и обладает специальными средствами автоматического обновления. С другой стороны, диапазон допустимых значений для него намного уже.

На что обратить внимание при выборе типа данных:

  • Стараться использовать типы данных минимального размера, достаточного для их правильного хранения и представления. Как правило, меньшие по размеру типы данных быстрее, поскольку занимают меньше места на диске, в памяти и в кэше процессора.
  • Чем проще, тем лучше. C точки зрения системы, сравнение целых чисел проще сравнения символов, поскольку из-за различных кодировок и правил сортировки сравнение символов усложняется. Поэтому значения даты и времени лучше хранить во встроенных типах данных MySQL, а не в строках, а для IP-адресов имеет смысл использовать целочисленные типы данных.
  • Стараться избегать значений NULL. Для MySQL оптимизация запросов, содержащих допускающие NULL столбцы, вызывает дополнительные сложности, поскольку из-за них усложняются индексы, статистика индексов и сравнение значений. Столбец, допускающий NULL, занимает больше места на диске и требует специальной обработки внутри MySQL. Если есть необходимость отобразить в таблице факт отсутствия значения, можно обойтись без использования NULL. Вместо этого, к примеру, можно использовать 0, специальное значение или пустую строку.
  • Имеет смысл использовать в связанных столбцах одни и те же типы данных. Использование различных типов данных в связанных столбцах может замедлить обработку запроса. Скорость при соединении столбцов типа VARCHAR и ENUM:
Запросов в секунду
Соединение VARCHAR с VARCHAR2.6
Соединение VARCHAR с ENUM1.7
Соединение ENUM с VARCHAR1.8
Соединение ENUM с ENUM3.5

В целях совместимости MySQL поддерживает различные псевдонимы, например INTEGER, BOOL — это псевдонимы (синонимы) одного и того же типа данных. Данный факт может сбить с толку, но не оказывает влияния на производительность.

Числовые типы

  • BIT — можно использовать для хранения одного или нескольких значений true/false в одном столбце. BIT(1) определяет поле, содержащее один бит, BIT(2) — два бита и т. д. Максимальная длина столбца типа BIT равна 64 битам. До версии MySQL 5.0 слово BIT было синонимом TINYINT. Поведение типа BIT зависит от подсистемы хранения.

Хранение целых чисел

  • TINYINT(N) (синоним INTEGER, BOOL, BOOLEAN) — 8 бит;
  • SMALLINT(N) — 16 бит;
  • MEDIUMINT(N) — 24 бита;
  • INT(N) — 32 бита;
  • BIGINT(N) — 64 бита.

СУБД MySQL позволяет указывать для целых чисел «размер», например INT(11). Для большинства приложений это не имеет значения: диапазон возможных значений этим не ограничивается. Однако данный параметр говорит некоторым интерактивным инструментам MySQL, сколько позиций необходимо зарезервировать для вывода числа. С точки зрения хранения и вычисления INT(1) и INT(20) идентичны.

Целочисленный тип данных длиной N бит позволяет хранить значения от -2(N-1) до 2(N-1)-1.

Целые типы данных могут иметь необязательный атрибут UNSIGNED, запрещающий отрицательные значения и приблизительно вдвое увеличивающий верхний предел положительных значений. Например, тип TINYINT UNSIGNED позволяет хранить значения от 0 до 255, а не от -128 до 127.

Знаковые и беззнаковые типы требуют одинакового пространства и обладают одинаковой производительностью.

Необязательный атрибут ZEROFILL заполнит нулями свободные позиции слева. Например с TINYINT(3) ZEROFILL, величина 2 будет записана, как 002.

Тип данныхБитПо умолчаниюUNSIGNED
TINYINT8-128 — 1270 — 255
SMALLINT16-32768 — 327670 — 65535
MEDIUMINT24-8388608 — 83886070 — 16777215
INT32-2147483648 — 21474836470 — 4294967295
BIGINT64-9223372036854775808 — 92233720368547758070 — 18446744073709551615

Хранение дробных чисел

Все типы допускают приближенные математические вычисления с плавающей точкой, но в случае с FLOAT и DOUBLE операции выполняются быстрее, так как процессор выполняет их естественным для него образом.

  • FLOAT(M,D) — число с плавающей точкой небольшой точности, задействует 4 байта.
  • DOUBLE(M,D) (синонимы REAL, DOUBLE PRECISION) — число с плавающей точкой двойной точности. Задействует 8 байт, имеет большую точность и больший диапазон значений.
  • DECIMAL(M,D) (синонимы DEC, NUMERIC, FIXED) — дробное число, хранящееся в виде строки, если десятичное значение равно 0, значение не будет иметь десятичной запятой или дробной части. Предназначен для хранения точных дробных чисел (можно хранить большие целые числа, не помещающиеся в типе BIGINT). Имеет смысл использовать только тогда, когда нужны точные результаты при вычислениях с дробными числами, — например, при хранении финансовых данных. Задействует больше пространства.

M — количество отводимых под число символов. D — количество символов дробной части.

Для типов с плавающей точкой можно указать максимально разрешенное количество цифр до и после десятичной запятой. В случае с DECIMAL это влияет на объем пространства, требуемого для хранения данных столбца. При указании точности, в некоторых случаях, MySQL незаметно для пользователя может выбирать другой тип данных или будет округлять значения при сохранении. Поэтому рекомендуют указывать желаемый тип, но не точность.

FLOAT и DOUBLE могут иметь параметр UNSIGNED, запрещающий отрицательные числа, но диапазон значений от этого не изменится.

Число типа DECIMAL в MySQL 5.0 и более новых версиях может содержать до 65 цифр. В более ранних версиях MySQL тип DECIMAL имел предел 254 цифры и хранил значения в виде неупакованных строк (один байт на цифру). Однако эти версии СУБД не умели использовать такие большие числа в вычислениях, поскольку тип DECIMAL был просто форматом хранения. При выполнении каких-либо операций значения DECIMAL преобразовывались в тип DOUBLE.

Строковые типы

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

  • VARCHAR(N) — хранит символьные строки переменной длины и является наиболее общим строковым типом данных. Значение N может принимать значения от 0 до 65535 (до версии MySQL 5.0.3 значение N могло быть от 0 до 255). Строки этого типа могут занимать меньше места, чем строки фиксированной длины CHAR. Происходит это потому, что в VARCHAR используется лишь то количество места, которое действительно необходимо (за исключением таблиц у которых задан фиксированный размер строк). В типе VARCHAR используется один или два дополнительных байта для хранения длины строки: один байт, если максимальная длина строки в столбце не превышает 255 байт, и два байта в случае более длинных строк. Т.е. тип VARCHAR(10) может занимать до 11 байт. Тип VARCHAR(1000) занимает до 1002 байт, поскольку в данном случае для хранения информации о длине строки требуется два байта. VARCHAR увеличивает производительность за счет меньшего потребления места на диске. Однако поскольку строки имеют переменную длину, они способны увеличиваться при обновлении, что вызывает дополнительную нагрузку. Если строка становится длиннее и больше не помещается в ранее отведенное для нее место, то ее дальнейшее поведение зависит от подсистемы хранения. Обычно имеет смысл использовать тип VARCHAR при соблюдении хотя бы одного из следующих условий: максимальная длина строки в столбце значительно больше средней; обновление поля выполняется редко, так что фрагментация не представляет проблемы; либо используется сложная кодировка, например UTF-8, в которой для хранения одного символа используется переменное количество байтов.
  • CHAR(N) — имеет фиксированную длину, от 0 до 255 байт. При сохранении коротких значений CHAR они дополняются справа пробелами до указанной длины. Тип CHAR полезен, когда требуется сохранять очень короткие строки или все значения имеют приблизительно одинаковую длину. Например, CHAR является хорошим выбором для хранения MD5-сверток паролей пользователей, которые всегда имеют одинаковую длину. Тип CHAR также имеет преимущество над VARCHAR для часто меняющихся данных, поскольку строка фиксированной длины не подвержена фрагментации. В случае очень коротких столбцов тип CHAR также эффективнее, чем VARCHAR.

При создании таблицы нельзя комбинировать столбцы типов CHAR и VARCHAR. Если такое произойдет, то MySQL изменит тип столбцов CHAR на тип VARCHAR.

Представим что строковый тип применяется для хранения значений Y и N. В случае использования CHAR(1) значение займет один байт, тогда как для типа VARCHAR(1) потребуется два байта из-за наличия дополнительного байта длины строки.

ЗначениеCHAR(4)Требуется хранилищеVARCHAR(4)Требуется хранилище
»‘ ‘4 байта»1 байт
‘ab’‘ab ‘4 байта‘ab’3 байта
‘abcd’‘abcd’4 байта‘abcd’5 байт
‘abcdefgh’‘abcd’4 байта‘abcd’5 байт

Двоичные строки

Для совместимости со старыми версиями MySQL введены два специальных типа данных: BINARY и VARBINARY, которые эквивалентны типам CHAR и VARHAR, однако строка в них рассматривается как последовательность байтов, а не символов. К BINARY строкам не применимы кодировки и сортируются они как обычные последовательности байтов. Эти типы могут быть полезны, когда нужно сохранять двоичные данные, и вы хотите, чтобы MySQL сравнивал значение как байты, а не как символы. При этом, двоичное сравнение может оказаться значительно проще и быстрее символьного.

  • VARBINARY — хранит бинарные строки переменной длины.
  • BINARY — хранит бинарные строки фиксированной длины.

Текстовые и бинарные типы

Предназначены для хранения больших объемов двоичных или символьных данных.

MySQL обрабатывает значения BLOB и TEXT как отдельные объекты. Единственное различие между семействами BLOB и TEXT заключается в том, что типы BLOB хранят двоичные данные без учета схемы упорядочения и кодировки, а с типами TEXT ассоциированы схемы упорядочения и кодировка.

Семейство TEXT используется для хранения непосредственно текста:

Cемейство BLOB — для хранения изображений, звука, электронных документов и т.д.:

MySQL не может индексировать данные этих типов по полной длине и не может использовать для сортировки индексы.

Подсистема хранения Memory не поддерживает типы BLOB и TEXT.

Типы данныхМакс. размер.Байт
TINYTEXT или TINYBLOB2 8 -1255
TEXT или BLOB2 16 -1 (64K-1)65535
MEDIUMTEXT или MEDIUMBLOB2 24 -1 (16M-1)16777215
LONGTEXT или LONGBLOB2 32 -1 (4G-1)4294967295

Составные типы

  • ENUM(‘value1’, ‘value2’, . ‘valueN’) — строки этого типа могут приниматьтолько одно из значений указанного множества. Можно хранить до 65 535 различных строковых значений. MySQL сохраняет их очень компактно, упаковывая в 1 или 2 байта, в зависимости от количества значений в списке. MySQL воспринимает каждое значение как целое число, представляющее позицию значения в списке значений поля, и отдельно хранит в frm-файле «справочную таблицу», определяющую соответствие между числом и строкой. Поля типа ENUM сортируются по внутренним целочисленным значениям, а не по самим строкам. Главным недостатком столбцов типа ENUM является то, что список строк фиксирован, а для их добавления или удаления необходимо использовать команду ALTER TABLE. Этот тип данных удобно использовать, если в столбце должен храниться выбор из списка или ответ на вопрос.
  • SET(‘value1’, ‘value2’, . ‘valueN’) — строки этого типа могут принимать любое или все элементы из значений указанного множества. Как правло, при поиске в столбцах типа SET не используются индексы. Можно хранить до 64 различных строковых значений. Может занимать до 8 байт, в зависимости от количества значений в списке.

Временные типы

  • DATE — предназначен для хранения даты. Формат: год "YYYY", разделитель, месяц "ММ", разделитель, день "DD". В качестве разделителя может выступать не только дефис «-», но и любой символ отличный от цифры.
  • DATETIME — предназначен для хранения и даты и времени суток. Позволяет хранить значения в большом диапазоне, с 1001 до 9999 года, с точностью в одну секунду. Дата и время упаковываются в целое число в формате YYYYMMDDHHMMSS независимо от часового пояса. Под значение отводится восемь байт. По умолчанию MySQL показывает данные типа DATETIME в точно определенном, допускающем сортировку формате: . Этот способ представления даты и времени согласуется со стандартом ANSI.
  • TIME — предназначен для хранения времени суток. Значение вводится и хранится в привычном формате: hh:mm:ss, где hh — часы, mm — минуты, ss — секунды. В качестве разделителя может выступать любой символ отличный от цифры.
  • TIMESTAMP — предназначен для хранения даты и времени суток в виде количества секунд, прошедших с полуночи 1 января 1970 года по гринвичскому времени (начало эпохи UNIX). С точки зрения занимаемого места на диске он гораздо эффективнее, чем DATETIME. Для хранения типа TIMESTAMP используется только четыре байта, поэтому он позволяет представить значительно меньший диапазон дат, чем тип DATETIME: с 1970 года до некоторой даты в 2038 году. В MySQL имеют ся функции FROM_UNIXTIME() и UNIX_TIMESTAMP(), служащие для преобразования временной метки UNIX в дату и наоборот. Столбцы типа TIMESTAMP по умолчанию создаются в режиме NOT NULL, если вы не указали значение для столбца, MySQL вставляет в первый столбец типа TIMESTAMP текущее время. Тип TIMESTAMP имеет также специальные свойства, которых нет у типа DATETIME.
  • YEAR(N) — предназначен для хранения года. Число N задает формат года: YEAR (2) — 70, а YEAR (4) — 1970. По умолчанию, N = 4.
Типы данныхФормат
DATETIME‘’
DATE‘0000-00-00’
TIMESTAMP00000000000000 (длина зависит от количества выводимых символов)
TIME‘00:00:00’
YEAR0000

Особенности строк в .NET

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

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

В.NET строки располагаются согласно правилу BSTR (Basic string or binary string). Данный способ представления строковых данных используется в COM (слово basic от языка программирования VisualBasic, в котором он первоначально использовался). Как известно в C/C++ для представления строк используется PWSZ, что расшифровывается как Pointer to Wide-character String, Zero-terminated.

При таком расположении в памяти в конце строки находится null-терминированный символ, по которому мы можем определить конец строки. Длина строки в PWSZ ограничена лишь объемом свободной памяти. С BSTR дело обстоит немного иначе. Основные особенности BSTR представления строки в памяти:

  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) (деление естественно целочисленное)

Вопрос версий: В .NET до 4 версии в классе String хранится дополнительное поле m_arrayLength типа int, которое занимает 4 байта. Данное поле есть реальная длина буфера выделенного под строку включая null — терминированный символ, то есть это length + 1. В .NET 4.0 данное поля удалено из класса, в результате чего объект строкового типа занимает на 4 байта меньше.

Размер пустой строки без поля m_arrayLength(то есть в .NET 4.0 и выше) равен = 4 + 4 + 4 + 2 = 14 байт, а с этим полем (то есть ниже .NET 4.0) равен = 4 + 4 + 4 + 4 + 2 = 18 байт. Если округлять по 4 байта то 16 и 20 байт соответственно.

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

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

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

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

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

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

  • Строковый тип является потокобезопасным, так как ни один поток не может изменить содержимое строки.
  • Использование неизменных строк ведет к снижению нагрузки на память, так как нет необходимости хранить 2 экземпляра одной строки. В таком случае и памяти меньше расходуется, и сравнение происходит быстрее, так как требует сравнение лишь ссылок. Механизм, который это реализует в .NET называется интернированием строк (пул строк), о нем поговорим чуть позже.
  • При передаче неизменяемого параметра в метод мы можем не беспокоиться, что он будет изменен (если, конечно, он не был передан как ref или out).

Учитывая, что строки неизменны, они могли бы быть и персистентными, однако таковыми не являются. В .NET строки являются эфемерными. Подробнее о том, почему это именно так можно прочитать у Эрика Липперта по ссылке

Для сравнения возьмем строки Java. Они являются неизменяемыми, как и в .NET, но вдобавок и персистентными. Реализация класса String в Java выглядит так:

re>public final class String

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

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

re>public String substring(int beginIndex, int endIndex) < if (beginIndex < 0) throw new StringIndexOutOfBoundsException(beginIndex); if (endIndex >count) throw new StringIndexOutOfBoundsException(endIndex); if (beginIndex > endIndex) throw new StringIndexOutOfBoundsException(endIndex — beginIndex); return ((beginIndex == 0) (endIndex == count)) ? this : new String(offset + beginIndex, endIndex — beginIndex, value); > public String(int offset, int count, char value[])

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

s = ss.substring(3)

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

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

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

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

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

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

re>if (s1 == s2)

re>if (s1.Equals(s2))

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

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

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

re>var s = "Strings are immutuble"; int length = s.Length; for (int i = 0; i < length / 2; i++)

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

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

re>var s = "Strings are immutable"; int length = s.Length; unsafe < fixed (char* c = s) < for (int i = 0; i < length / 2; i++) < var temp = c[i]; c[i] = c[length - i - 1]; c[length - i - 1] = temp; >> >

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

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

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

re>var s1 = "habrahabr"; var s2 = "habrahabr"; var s3 = "habra" + "habr"; Console.WriteLine(object.ReferenceEquals(s1, s2));//true Console.WriteLine(object.ReferenceEquals(s1, s3));//true

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

re>var s = "Strings are immutable"; int length = s.Length; unsafe < fixed (char* c = s) < for (int i = 0; i < length / 2; i++) < var temp = c[i]; c[i] = c[length - i - 1]; c[length - i - 1] = temp; >> > Console.WriteLine("Strings are immutable");

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

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

Кстати, этот атрибут не обрабатывается в .NET Framework версии 1.0., поэтому отключить интернирование по умолчанию не было возможным. Сборка mscorlib, начиная со второй версии, помечена этим атрибутом.

Получается, что строки в .NET все-таки можно изменить, если очень захотеть, применяя unsafe код.

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

Оказывается, изменить содержимое строки было возможно и, не прибегая к unsafe коду, воспользовавшись механизмом рефлексии. Этот трюк мог прокатить в .NET до 2.0 версии включительно, потом разработчики класса String лишили нас такой возможности. В версии .NET 2.0 у класса String есть два internal метода: SetChar, проверяющий выход за границы, и InternalSetCharNoBoundsCheck, не проверяющий выход за границы, которые устанавливают указанный символ по определенному индексу. Вот их имплементация:

re>internal unsafe void SetChar(int index, char value) < if ((uint)index >= (uint)this.Length) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index")); fixed (char* chPtr = > internal unsafe void InternalSetCharNoBoundsCheck (int index, char value)

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

re>var s = "Strings are immutable"; int length = s.Length; var method = typeof(string).GetMethod("InternalSetCharNoBoundsCheck", BindingFlags.Instance | BindingFlags.NonPublic); for (int i = 0; i < length / 2; i++) < var temp = s[i]; method.Invoke(s, new object[] < i, s[length - i - 1] >); method.Invoke(s, new object[] < length - i - 1, temp >); > Console.WriteLine("Strings are immutable");

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

Вопрос версий: В разных версиях .NET Framework string.Empty может интернироваться, а может, и нет. Рассмотрим код:

re>string str1 = String.Empty; StringBuilder sb = new StringBuilder().Append(String.Empty); string str2 = String.Intern(sb.ToString()); if (object.ReferenceEquals(str1, str2)) Console.WriteLine("Equal"); else Console.WriteLine("Not Equal");

В .NET Framework 1.0, .NET Framework 1.1 и .NET Framework 3.5 с пакетом обновления 1 (SP1), str1 и str2 равны. В .NET Framework 2.0 с пакетом обновления 1 (SP1) и .NET Framework 3.0, str1 и str2 не равны. В настоящее время string.Empty интернируется.

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

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

Надеюсь, статья оказалось полезной.

  • строки
  • структура данных
  • особенности

Значение типа word для хранения длины строки в байтах

Строки. Типы данных STRING

Строка. Тип данных STRING

Строка (string) – это последовательность литер. Тип данных (string) определяет строки с максимальной длиной 255 символов. Переменная этого типа может принимать значения переменной длины.

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

Текущая длина строковой переменной может быть определена с помощью встроенной функции Length . для заданного значения типа string эта функция возвращает целое значение, показывающее количество литер в строке. Выражения, в которых операндами служат строки, называются строковыми выражениями.

Над строками определены две операции:

1. Операция сцепления (+) применяется для сцепления нескольких строк в одну.

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

Строки считаются равными , если они совпадают по длине и содержат одни и те же символы на соответствующих местах в строке.

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

Допускается смешение в одном выражении операндов строкового и символьного типа.

К отдельным символам строки можно обратиться по номеру (индексу) данного символа в строке.

Например, чтобы обратиться к третьему символу строки SumStr надо записать SumStr[3]. Запись SumStr[0] дает значение текущей длины строки.

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

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

Для обработки строковых данных можно использовать встроенные процедуры и функции:

1. Delete (Str,Poz,N) – удаление N символов строки Str, начиная с позиции Poz.

2. Insert (What,Where,Poz) – вставка строки What в строку Where, начиная с позиции Poz.

3. Copy (Str,Poz,Nstr) – выделяет строку длиной Nstr, начиная с позиции Poz, из строки Str.

4. Concat (Str1,Str2. StrN) – выполняет сцепление строк в том порядке, в каком указаны в списке параметров.

5. Poz (What,Where) – обнаруживает первое появление подстроки What в строке Where.

6. UpCase (Ch) – преобразует строчную букву в прописную.

7. Str (Number,Stroka) – преобразует число в строку.

8. Val (Stroka,Number,Code) – преобразует строку в число и выдает код правильности преобразования.

Оцените статью
InternetDoc.ru
Добавить комментарий