Pull to refresh

Comments 38

каждая строка занимает 20+(n/2)×4

А можно поподробней, почему n/2?
В статье Джон Скит не расписывает механизм хранения строк детально, да и я не могу утверждать, что являюсь экспертом во внутренностях .NET, однако полагаю, формула 20+(n/2)×4 связана с тем, что каждый символ хранится в UTF-16 и занимает 2 байта. 20 — это вспомогательная информация. А вот почему (n/2)×4, а не n×2 — к сожалению, не знаю.
Возможно, символы как раз хранятся по 2 в 32битных ячейках. Соответственно, суррогатные пары хранятся каждая в целой ячейке, а формула их просто не учитывает.
Возможно, память выделяется дискретами по 32 бита?
А вот почему (n/2)×4, а не n×2 — к сожалению, не знаю.

Есть мнение, что это оптимизация доступа к памяти.
Ну по виду обычное выравнивание в 4 байта.
В оригинальной статье написано:

> strings take up 20+(n/2)*4 bytes (rounding the value of n/2 down)

(n / 2) — целочисленное деление (13 / 2 == 6; 12 / 2 == 6)
В таком случае, строка, длинной в 1 символ, будет занимать 20 + (1/2) * 4 = 20 байт? Значит один из символов входит во вспомогательную информацию?
Ну это вопрос не ко мне, а к Джону Скиту или разработчикам CLR :)
Округлить 0.5 в меньшую сторону забыли.
Ну почему же забыли!
Возможно, это 16 + (n/2+1)*4 = 20 + (n/2)*4
ничего не забыл, так что все верно, вообще в таких случаях нужно всегда 1-м делать умножение и потом уже делить, тогда результат будет намного точнее
Вы видели rounding the value of n/2 down?
И как можно в «таких случаях» делать умножение первым, когда скобки явно указывают на порядок вычисления?
В данном случае либо недопонимание или автор не дорассказал. Если n = 1, то получается 0, что неверно, не может один символ храниться в 0 байтах, а если n = 3, то получится 4 байта, как вы себе представляете 3 символа в 4-х байтах? Ну и т. д.
Формулу (n/2)*4 нужно читать как два символа в 4-х байтах, причем конечный результат всегда выровнен по границе 4, а для этого нужно было применять такую формулу ((n+1)/2) * 4
Легко, если на самом деле данные об объекте занимают 18 байт (но в другой статье Скит говорит о 14 байтах), а выровнено в 4 байта, тогда два байта из 20 можно использовать под один символ. И формулу нужно читать как Math.Floor(n/2)*4.
Хабр — такой хабр :)
Полемика по поводу пары байт скоро перерастет в несколько гигабайт, хранящихся в БД Хабра в виде комментариев )
Оказывается все проще: пустая строка тоже занимает 2 байта (т.е. значение null). А его формула просто «убирает» из 20 эти 2 байта с помощью округления, если строка не null.

Сам Скит скинул ссылку на более новую статью, где размер уже высчитывается как 14 + length * 2 для x86. Какая статья точнее он не уверен.
Хотя нет, null здесь не при чем, действительно простое выравнивание.
Нашёл такую информацию:
A string is composed of:
• An 8-byte object header (4-byte SyncBlock and a 4-byte type descriptor)
• An int32 field for the length of the string (this is returned by String.Length).
• An int32 field for the number of chars in the character buffer.
• The first character of the string in a System.Char.
• The rest of the string in a character buffer, terminating with a null terminator char.

Насколько правдива и актуальна — неизвестно.
Небольшой тест в Visual Studio под .NET Framework 4.5 и x64 показал, что объект String состоит из:
• 4-byte SyncBlock
• 8-byte type descriptor (или 4 byte для 32-bit)
• An int32 field — длина строки
• символы по 2 байта, без null terminator
По поводу интернирования можно было бы добавить, что его можно отлючить специальным атрибутом для проекта.
Интересно, в чём главные отличия между JVM и .NET intern pool'ом?
Если внимательно вчитаться, то можно увидеть, что имелось ввиду, что интернирование является фичей не конкретного языка на платформе .NET, а платформы в целом. О его наличии или отсутствии в других языках или платформах не сказано ровным счётом ничего.
>>Метод IndexOf будет учитывать эсцет как «ss» (двойное «s»), но вот если вы используете одну из перегрузок CompareInfo.IndexOf, где укажете CompareOptions.Ordinal, то эсцет будет обработан правильно.

Странно в данном случае говорить о «правильности». Это особенности, которые надо знать (не все из них я знаю), но то что Zurich и Zürich — одно и тоже вполне нормально. Про такое поведение, насколько я помню, написано в CLR via C#, и плох разработчик, который ни разу его не читал.
В оригинале это предложение звучит как «IndexOf will treat the eszett as the same as „ss“, unless you use a CompareInfo.IndexOf and specify CompareOptions.Ordinal as the options to use.», слово «правильно» подобрал я при переводе. Вместе с тем, насколько я понял Джона Скита, его «смущает» (если можно так выразиться) не столько то, как обрабатываются подобные символы, а то, что разными функциями они обрабатываются по-разному.
В данном случае все логично. Есть поведение по-умолчанию, но иногда же надо работать с точностью до символа, для таких случаев есть опции.
В случае Equals и Compare тоже все логично. Согласно МСДН: первый сравнивает значения, второй определяет алфавитный порядок. Е и Ё, к примеру, тоже приравниваются в словарях (бумажных).
Это все — логичное поведение, таким образом спроектированное. Другой вопрос, что при переходе с того же c++ (или другого языка постарше) это непривычное поведение.
Тем, что не читал. Там множество подобных тонкостей расписано.
> Никак невозможно изменить содержимое созданной строки, по крайней мере в управляемом коде и без рефлексии.
Можно, используя unsafe.
Пошёл смотреть оригинал, там правильно: «safe code» переводится не «управляемый» (это managed), а «безопасный».
Кто не успел, тот опоздал :) Если серьезно, то в Джона Скита вполне достаточно прекрасных статей для перевода. Вы можете сообщить мне, которые статьи вы желаете перевести в будущем, и я не буду их трогать.
… символом Юникода в диапазоне от U+0000 до U+FFFF ...

В оригинальной статье написано «character» (может для упрощения понимая?), хотя по стандарту Unicode правильно это называется «code point», т.к. там кроме «characters» есть куча управляемых непечатаемых символов. Всегда интересно было как корректно перевести «code point» на русский…
(англ. zero-width non-joiner character), что бы это не значило, чёрт возьми!


В некоторых языках, например в арабском, буквы одного слова могут менять свое начертание в зависимости от того, в какой части слова они находятся, т.н. «вязь». Вставленный между двумя буквами, non-joiner отключает этот механизм и буквы будут отображены так, как если бы они были написаны сами по себе, вне слова. Zero-with означает, что сам non-joiner не отображается. Обработка joiner'ов, mark-ов и прочих спецсредств юникода как символов — это ошибка реализации библиотек.
Only those users with full accounts are able to leave comments. Log in, please.