Как работает интернирование строк
1) Intern pool; (пулСтрок) Позволяет объединить строки с одинаковыми значениями в них в определенный пул памяти. В данном случае мы инициализировали две строки с одинаковыми значениями и они попали в один пул памяти. пример в моем понимании:
Почему так? и как это работает? по сути мы получаем строку с одинаковыми значениями и все они должны ссылаться на один объект. почему мы по итогу получаем разные результаты? Я понимаю, что в данном случае мы сравниваем объекты, а не содержимое. Но почему они не объединились?
В первом вашем примере строки интернируются на этапе компиляции. Если посмотреть в утилите ILDASM, то в окне MetaInfo в разделе User Strings будет представлен всего один экземпляр:
Соответственно, обе переменные: и a , и b будут указывать на этот адрес.
Во втором примере в результате конкатенации тоже получается строка «aaa», но она не заносится по умолчанию в пул интернированных строк, потому что для этого нужны дополнительные проверки, то есть тратится время.
Во втором примере можно добавить строку в пул вручную:
Ответ на вопрос из комментария:
Я не уверен, но, думаю, выгода в том, что после интернирования переменная b станет указывать на тот же участок памяти, что и a . После чего сборщик мусора сможет убрать другой экземпляр строки «aaa». Это почти бессмысленно для коротких строк, но может оказаться выгодно для длинных долгоживущих строк.
Также нужно учитывать, что удалить строку из пула невозможно. Она так и будет висеть в памяти до конца работы приложения. Именно поэтому в рантайме строки не добавляются в пул по умолчанию.
Чтобы не гадать, глянем сразу IL-инструкции которые генерирует компилятор:
Как видим в данном случае в первом блоке мы записали одну и ту же строку в a (IL_0006) и b (IL_000C), и в данном случае строка является интернированной, во втором же блоке только ‘a2′(IL_0017) является интернированной, b2 после конкатенации (IL_0026) и присваивания (IL_002B) таковой не является.
UPD: Следующий код также возвращает false :
Метод ReferenceEquals определяет, совпадают ли указанные экземпляры Object . При сравнении строк. Если objA и objB являются строками, ReferenceEquals возвращает true если строки интернированы. Он не выполняет проверку на равенство значений. В следующем примере s1 и s2 равны, поскольку они являются двумя экземплярами одной интернированной строки. Тем не менее s3 и s4 не равны, поскольку несмотря на то, что они имеют идентичные строковые значения, эти строки не интернированы.
String.Intern делает строки ещё интереснее
Проходя/проводя собеседования, приходится сталкиваться с вопросами, которые раскрывают общее понимание работы .NET. По моему мнению, наибольшей любовью среди таких вопросов пользуются вопросы о работе “сборщика мусора”, но однажды мне был задан вопрос о интернировании строк. И он, честно говоря, поставил меня в тупик. Поиск в рунете выдал несколько статей, но они не давали ответы на те вопросы, которые я искал. Надеюсь мой перевод статьи Эндрю Стеллмана (автора книги “Head First C#”) заполнит этот пробел. Думаю, этот материал будет полезен для начинающих .NET разработчиков и тем кому стало интересно, что же такое интернирование строк в .NET.
String.Intern делает строки ещё интереснее
Одна из первых вещей, с которой сталкивается каждый начинающий C# разработчик — это работа со строками. Я показываю основу работы со строками в начале «Head First C#», как поступают практически в любой другой книге по C#. Так что не следует удивляться, что C# разработчики уровня джуниор и мидл уровня чувствуют, что они получили довольно хорошую базу по строкам. Но строки интереснее, чем кажутся. Одним из самых интересных аспектов строк в C# и .NET является метод String.Intern. Понимание работы этого метода может улучшить ваши навыки в C# разработке. В этом посте, я сделаю краткий туториал для метода String.Intern, чтобы показать вам как он работает.
Примечание: В конце этого поста я собираюсь показать кое-что «под капотом», используя ILDasm. Если вы никогда не работали с ILDasm раньше, это будет хорошей возможностью что-бы познакомиться с очень полезным инструментом .NET.
Некоторые основы работы со строками
Давайте начнем с краткого обзора того, что ожидают от класса System.String. (Я не буду вдаваться в подробности — если кто-то хочет пост о основах строк в .NET, добавьте комментарий или свяжитесь со мной на Building Better Software, и я буду рад обсудить возможную статью вместе!)
Создайте новое консольное приложение в Visual Studio. (Все точно так же работает из командной строки, если вы хотите использовать csc.exe для компиляции кода, но ради легкости восприятия материала давайте придерживаться разработки в Visual Studio.) Вот код метода Main() — точки входа консольного приложения:
В этом коде не должно быть никаких сюрпризов. Программа выводит три строки на консоль (помните, если вы работаете в Visual Studio, используйте Ctrl-F5, чтобы запустить программу вне отладчика; также в программу будет добавлено «Press any key . », что-бы предотвратить закрытие окна консоли):
hello, hello world
False
False
Первый WriteLine() выводит две строки. Второй сравнивает их с помощью оператора равенства ==, который возвращает False, потому что строки не совпадают. И последний сравнивает их, чтобы увидеть не ссылаются ли обе переменные на один и тот же объект String. Поскольку это не так, метод отображает значение False.
Затем добавьте эти две строки в конец метода Main():
И опять вы получите довольно очевидный ответ. Оператор равенства возвращает True, так как обе строки равны. Но когда вы использовали конкатенацию строк «Hello» и «world», оператор + объединяет их и возвращает новый экземпляр System.String. Именно поэтому object.ReferenceEquals() вполне резонно возвращает False. Метод ReferenceEquals() возвращает True только в том случае, если оба аргумента ссылаются на один и тот же объект.
Такой способ позволяет нормально работать с объектами. Два разных объекта могут иметь одинаковые значения. Такое поведение является вполне практичным и предсказуемым. Если вы создаете два объекта “дом” и установите всем их свойствам одинаковые значения, вы будете иметь два одинаковых объекта типа “дом”, но это будут различные объекты.
Это все еще кажется немного запутанным? Если так, то я определенно рекомендую обратить внимание на несколько первых глав “Head First C#”, которые дадут вам представление о написании программ, отладке, и использование объектов и классов. Вы можете скачать их как бесплатные вырезки из этой книги.
Итак, пока мы работаем со строками — все прекрасно. Но как только мы начинаем играться ссылками на строки, все становится немного странным.
Что-то с этой ссылкой не так .
Создайте новое консольное приложение. Код ниже для него. Но, перед компиляцией и выполнением, внимательно посмотрите на код. Попробуйте угадать, что он отобразит в консоли?
Теперь запустите программу. Вот то, что она отобразит в консоли:
hello world, hello world: True, False
И так, это именно то, что мы ожидали. В объектах helloWorld и helloWorld2 строки содержат “Hello world», так что они равны, но ссылки разные.
Теперь добавьте в нижней части вашей программы этот код:
Запустите его. На этот раз код отобразит в консоли следующую строку:
hello world, hello world: True, True
Подождите, получается что сейчас HelloWorld и HelloWorld2 ссылаться на одну и ту же строку? Наверное, некоторым может показаться такое поведение странным или, по крайней мере, немного неожиданным. Мы не меняли значение helloWorld2 вообще. Многие в конечном итоге думают что-то вроде этого: “переменная была уже равна «hello world». Установка в «hello world» ещё один раз не должна ничего изменить.” Так в чем же дело? Давайте разберёмся.
Что такое String.Intern? (погружаясь в пул интернирования . )
При использовании строк в C#, CLR делает что-то хитрое и это что-то называется интернирование строк. Это способ хранения одной копии любой строки. Если вы храните в ста или, что еще хуже, в миллионе строковых переменных одинаковое значение получится, что память для хранения значений строк будет выделяться снова и снова. Интернирование строки это способ обойти эту проблему. Среда CLR поддерживает таблицу называемую пул интернирования. Эта таблица содержит одну уникальную ссылку на каждую строку, которая либо объявлена, либо создана программно во время выполнения вашей программы. А .NET Framework предоставляет два полезных метода для взаимодействия с пулом интернирования: String.Intern() и String.IsInterned().
Метод String.Intern() работает очень простым способом. Вы передадите ему в качестве аргумента строку. Если эта строка уже находится в пуле интернирования, метод возвращает ссылку на эту строку. Если её еще не нет, он добавляет строку в пул и возвращает на неё ссылку. Вот пример:
Этот код будет отображать True, даже если HelloWorld и HelloWorld2 ссылки на два разных строковых объекта, потому что они оба содержат строку «Hello World».
Остановитесь на минутку. Стоит ещё немного поразбираться с String.Intern() потому, что иногда метод дает немного нелогичные на первый взгляд результаты. Вот пример такого поведения:
Выполнение кода выведет две строки на консоль. Первый метод WriteLine() покажет значение False, и это понятно, так как метод String.Copy() создает новую копию строки и возвращает ссылку на новый объект. Но почему выполнив вначале String.Intern(о.ToString()) затем String.Intern(a) вернёт ссылку на о? Остановитесь на минутку, чтобы подумать об этом. Это становится еще более нелогичным, если вы добавите еще три строки:
Похоже, эти строчки кода сделали то же самое, только с новой переменной объекта o2. Но в последнем WriteLine() выведет значение False. Так что же происходит?
Этот небольшой беспорядок поможет нам разобраться, что происходит под капотом String.Intern() и пула интернирования. Первое, что необходимо уяснить для себя это то, что метод строкового объекта в ToString() всегда возвращает ссылку на самого себя. Переменная o указывает на объект строки, содержащий значение «abc», поэтому вызов собственного метода ToString() возвращает ссылку на эту строку. Итак, вот что происходит.
В начале а указывает на объект строки №1, в котором содержится «abc». Переменная о указывает на другой объект строки №2 который также содержит «abc». Вызов String.Intern(o.ToString()) добавляет ссылку на строку №2 в пул интернирования. Теперь, когда объект строки №2 находится в пуле интернирования, в любое время String.Intern() вызывая с параметром «abc» будет возвращать ссылку на объект строки №2.
Поэтому, когда вы передаёте пременную о и String.Intern(а) в метод ReferenceEquals(), он возвращает True, потому что String.Intern(а) вернула ссылку на объект строки №2. Теперь мы создали новую переменную o2 и использовали метод String.Copy(), что бы создать еще один объект типа String. Это будет объект строки №3, который также содержит строку «abc». Вызов String.Intern(o2.ToString()) ничего не добавляет к пулу интернирования на этот раз, потому что «abc» уже есть, но вернёт указатель на строку №2.
Так что этот вызов Intern() фактически возвращает ссылку на строку №2, но мы отбрасываем его вместо того, чтобы присвоить переменной. Мы могли бы сделать что-то вроде этого: string q = String.Intern(o2.ToString()), что сделало бы переменную q ссылкой на объект строки №2. Именно поэтому, последний WriteLine() выводит False так как это сравнение ссылки строки №3 со ссылкой на строку №2.
Используйте String.IsInterned() для проверки, является ли строка в пуле интернирования
Есть другой, несколько парадоксально названный метод, который полезен при работе с интернированными строками: String.IsInterned(). Он принимает ссылку на объект строки. Если эта строка находится в пуле интернирования, он возвращает ссылку на интернированную строку строки, если она еще не находится в пуле интернирования, то метод возвращает null.
Причина, по которой его название звучит немного нелогичным в том, что этот метод начинается с «Is» но при этом возвращает не булев тип, как ожидают многие программисты.
При работе с методом IsInterned() для отображения того что строка отсутствует в пуле интернирования удобно использовать null-коалесцирующий оператор — ??. К примеру написав:
Теперь в переменную о вернется результат IsInterned() если он не нулевой, или строка «not interned», если строки нет в пуле интернирования.
Если этого не сделать, то метод Console.WriteLine() будет выводить пустые строки (что делает этот метод, когда сталкивается null).
Вот простой пример того, как String.IsInterned() работает:
Первый WriteLine() оператор отобразит в консоли «not interned», потому что «xyz» еще нет в пуле интернирования. Второй WriteLine() оператор печатает «xyz» потому, что пул интернирования уже содержит «xyz». И третий WriteLine () выведет True, так как объект s указывает на объект, добавленный в пул интернирования.
Литералы интернируются автоматически
Добавив всего одну строку в конец метода и запустив программу снова:
произойдет что-то совсем неожиданное!
Программа никогда не отобразит «not interned», а последние два метода WriteLine() покажут False! Если мы закомментируем последнюю строку, то программа действует именно так, как вы ожидали. Почему?! Как добавив код в конце программы, поменялось поведение программы кода над ним? Это очень, очень странно!
Это кажется действительно странным в первый раз, когда вы сталкиваетесь с этим, но в этом действительно есть смысл. Причина изменения поведения всей программы в том, что код содержит литерал «xyz». А когда вы добавляете литерал в вашу программу, CLR автоматически добавляет его в пул интернирования ещё до начала выполнения программы. Комментируете эту строку, вы убираете литерал из программы и пул интернирования уже не будет содержать строку «xyz».
Понимая, что «xyz» уже находится в пуле интернирования при запуске программы, так как эта строка в виде литерала появилась в коде, то сразу стаёт понятным такое изменение в поведении программы. String.IsInterned(s) больше не возвращает null. Вместо этого, он возвращает ссылку на литерал «xyz», что также объясняет, почему ReferenceEquals() возвращает False. Это происходит из за того, что строка s никогда не будет добавлена в пул интернирования («xyz» уже в пуле, указывая на другой объект).
Компилятор умнее, чем вы думаете!
Измените последнюю строку кода на эту:
Запустите программу. Она работает точно так же, как если бы вы использовали литерал «xyz»! Неужели + не оператор? Разве это не метод, который запускается на выполнение по CLR во время выполнения? Если это так, то должен быть код, который предотвратит интернирование литерала «xyz».
В самом деле так и произойдёт если вы замените «х» + «у» + «z» на String.Format(«<0><1><2>«, ‘x’, ‘y’, ‘z’). Обе строчки кода возвращают «xyz». Почему же при помощи оператора + для конкатенации получаем поведение, как если бы вы использовали литерал «xyz», хотя в тоже самое время как String.Format() выполняется во время выполнения?
Самый простой способ ответить на этот вопрос — это увидеть то, что на самом деле получаем при компиляции кода «x» + «у» + «z» .
Следующим шагом нужно выяснить, что компилятор собрал приложение исполняемого типа. Для этого мы будем использовать ILDasm.exe, дизассемблер MSIL. Этот инструмент устанавливается с каждой версией Visual Studio (в том числе и изданий Express). И даже если вы не знаете, как читать IL, вы сможете понять, что происходит.
Запустите Ildasm.exe. Если вы используете 64-разрядную версию Windows, выполните следующую команду: «%ProgramFiles (x86)%\Microsoft SDKs\Windows\v7.0A\Bin\Ildasm.exe» (включая кавычки), либо из Пуск >> окно Run, либо из командной строки. Если вы используете 32-разрядную версию Windows, вам стоит выполнить следующую команду: «%ProgramFiles%\Microsoft SDKs\Windows\v7.0A\Bin\ildasm.exe».
Вот так, выглядит ILDasm при первом запуске:

Затем скомпилируйте свой код в исполняемый файл. Кликните на проект в Solution Explorer — в окне Properties должно располагаться поле Project Folder. Дважды кликните по нему и скопируйте. Перейдя в окно ILDasm, выберите Файл >> Открыть в меню, и вставьте путь к папке. Затем перейдите в папку «bin». Ваш исполняемый файл должен находиться либо в папке bin\Debug или bin\Release. Откройте исполнимый файл. ILDasm должен показать вам содержимое сборки.

(Если вам нужно освежить память о том, как создаются сборки, см. этот пост для понимания C# и .NET сборок и пространств имен ).
Разверните класс Program и дважды щелкните на методе Main(). После этих действий должен появиться дизасамблированный код метода:

Вам не нужно знать IL чтобы увидеть наличие литерала «xyz» в коде. Если закрыть ILDasm, а затем изменить код, чтобы использовать «xyz» вместо «х» + «у» + «z», разобрали IL код выглядит точно так же! Это потому, что компилятор достаточно умен, чтобы заменить «х» + «у» + «z» на «xyz» во время компиляции, так что не придётся тратить лишние операции на вызовы методов, которые всегда будет возвращать «xyz». А когда литерал компилируется в программе, то CLR добавляет его в пул интернирования при запуске программы.
Rukovodstvo
статьи и идеи для разработчиков программного обеспечения и веб-разработчиков.
Руководство по интернированию строк в Python
Введение Одна из первых вещей, с которой вы сталкиваетесь при изучении основ программирования, — это концепция строк. Подобно различным языкам программирования, строки Python представляют собой массивы байтов, представляющие символы Unicode — массив или последовательность символов. Python, в отличие от многих языков программирования, не имеет отдельного символьного типа данных, и символы считаются строками длины 1. Вы можете определить строку, используя одинарные или двойные кавычки, например, a = "Hello World"
Время чтения: 8 мин.
Вступление
Одна из первых вещей, с которой вы сталкиваетесь при изучении основ программирования, — это концепция строк. Подобно различным языкам программирования, строки Python представляют собой массивы байтов, представляющие символы Unicode — массив или последовательность символов. Python, в отличие от многих языков программирования, не имеет определенного символьного типа данных, и символы считаются строками длиной 1.
Вы можете определить строку, используя одинарные или двойные кавычки, например, a = "Hello World" или a = ‘Hello World’ . Чтобы получить доступ к определенному элементу строки, вы должны использовать квадратные скобки ( [] ) с индексом символа, к которому вы хотите получить доступ (индексирование начинается с 0). Например, вызов a[0] H
При этом давайте взглянем на этот пример кода:
Все сравниваемые строки содержат значение Hello World ( a , b и c +’d’ ). Было бы интуитивно понятно предположить, что вывод будет True для всех этих операторов.
Однако, когда мы запускаем код, это приводит к:
Что может показаться неинтуитивным в этом выводе, так это то, что a is c + ‘d’ возвращает False , в то время как очень похожий оператор a is b возвращает True . Таким образом, мы можем заключить, что a и b — это один и тот же объект, а c — другой, даже если они имеют одинаковое значение.
Если вы не знакомы с различием между == и is — is проверяет, ссылаются ли переменные на один и тот же объект в памяти , а == проверяет, имеют ли переменные одинаковое значение .
Это различие между a , b и c является результатом интернирования строк .
Примечание . Среда, в которой вы запускаете код, влияет на то, как работает интернирование строк. Предыдущие примеры были результатом выполнения кода как сценария в неинтерактивной среде с использованием последней версии Python (версия 3.8.5). Поведение будет отличаться при использовании консоли / Jupyter из-за разных способов оптимизации кода или даже между разными версиями Python.
Это связано с тем, что разные среды имеют разные уровни оптимизации.
Строка интернирования
Строки в Python — это неизменяемые объекты. Это означает, что после создания строк мы не можем их изменять или обновлять. Даже если кажется, что строка была изменена, под капотом была создана копия с измененным значением и присвоена переменной, в то время как исходная строка осталась прежней.
Попробуем изменить строку:
Поскольку name строки неизменяемо, этот код завершится ошибкой в последней строке:
Примечание: если вы действительно хотите изменить конкретный символ строки, вы можете преобразовать строку в изменяемый объект, например list , и изменить желаемый элемент:
Что дает нам желаемый результат:
Причина, по которой мы могли изменить символ в списке (а не в строке), заключается в том, что списки изменяемы, что означает, что мы можем изменять их элементы.
Интернирование строк — это процесс сохранения в памяти только одной копии каждого отдельного строкового значения.
Это означает, что когда мы создаем две строки с одним и тем же значением
- вместо того, чтобы выделять память для них обеих, только одна строка фактически сохраняется в памяти. Другой просто указывает на то же место в памяти.
Учитывая эту информацию, вернемся к исходному примеру Hello World
Когда a строка a, компилятор проверяет, присутствует ли Hello World во внутренней памяти. Так как это первое вхождение этого строкового значения, Python , создает объект и кэширует эту строку в памяти и указывает этой ссылке. a
Когда b создается, Hello World обнаруживается компилятором во внутренней памяти, поэтому вместо создания другой строки b просто указывает на ранее выделенную память.
a is b а в данном случае a == b
Наконец, когда мы создаем строку c = ‘Hello Worl’ , компилятор создает экземпляр другого объекта во внутренней памяти, потому что он не может найти тот же объект для ссылки.
Когда мы сравниваем a и c+’d’ , последнее оценивается как Hello World . Однако, поскольку Python не выполняет интернирование во время выполнения, вместо него создается новый объект. Таким образом, поскольку ни интернирование не было сделано, эти два не тот же объект , и is возвращает False .
В отличие от оператора is == сравнивает значения строк после вычисления выражений времени выполнения Hello World == Hello World .
В то время как a и c+’d’ одинаковы по значению, так что это возвращает True .
Проверка
Посмотрим идентификатор созданных нами строковых объектов. Функция id(object) в Python возвращает идентификатор object , который гарантированно будет уникальным в течение всего времени существования указанного объекта. Если две переменные указывают на один и тот же объект, вызывающий id вернет одно и то же число:
Только c имеет другой идентификатор. Все ссылки теперь указывают на объект с тем же значением Hello World Однако c вычислялась не во время компиляции, а во время выполнения. Даже d , который мы сгенерировали путем добавления ‘d’ теперь указывает на тот же объект, a указывают a и b
Как струны интернированы
В Python есть два способа интернирования строк на основе взаимодействия программиста:
- Неявное интернирование
- Явное интернирование
Неявное интернирование
Python автоматически обрабатывает некоторые строки в момент их создания. Будет ли строка интернирована или нет, зависит от нескольких факторов:
Интернируются все пустые строки и строки длины 1.
Вплоть до версии 3.7 Python использовал оптимизацию глазка, и все строки длиной более 20 символов не интернировались. Однако теперь он использует оптимизатор ASTÂ , и (большинство) строк длиной до 4096 символов интернированы.
Имена функций, классов, переменных, аргументов и т. Д. Неявно интернируются.
Ключи словарей, используемые для хранения атрибутов модуля, класса или экземпляра, интернированы.
Строки интернируются только во время компиляции, это означает, что они не будут интернированы, если их значение не может быть вычислено во время компиляции.
- Эти строки будут интернированы, например:
- Следующее выражение вычисляется во время выполнения, поэтому строка не интернируется.
Строки, содержащие символы помимо ASCII, скорее всего, интернироваться не будут.
Если вы помните, мы говорили, что ‘Hello Worl’ + letter_d было вычислено во время выполнения, и поэтому оно не будет интернировано. Поскольку не существует единого стандарта интернирования строк, хорошим практическим правилом является идея времени компиляции / времени выполнения, когда вы можете предположить, что строка будет интернирована, если ее можно вычислить во время компиляции.
Явное интернирование
Мы часто сталкиваемся со строками, которые не лежат в условиях неявного интернирования в Python, но есть способ интернировать любую строку, которую вы хотите. В модуле sys есть функция под intern(immutable_object) , эта функция сообщает Python, что нужно сохранить immutable_object (строка в нашем случае) в таблице интернированной памяти.
Вы можете интернировать любую строку следующим образом:
Мы видим, что это сработает в нашем предыдущем примере:
Теперь, когда мы знаем, как и какие строки интернируются в Python. Остается один вопрос — почему было введено интернирование строк?
Преимущества интернирования строк
Интернирование строк имеет несколько преимуществ:
- Сохранение памяти: нам никогда не нужно сохранять в памяти два строковых объекта по отдельности, если они одинаковы. Каждая новая переменная с тем же содержимым просто указывает на ссылку в интернированном табличном литерале. Если по какой-то причине вы хотите иметь список, содержащий каждое слово и его появление в книге Джейн Остин «Гордость и предубеждение» , без явного интернирования вам потребуется 4,006,559 байта, а с явным интернированием каждого слова вам понадобится только 785,509 байт памяти.
- Быстрые сравнения: сравнение интернированных строк намного быстрее, чем неинтернированных строк, что полезно, когда в вашей программе много сравнений. Это происходит потому, что для сравнения интернированных строк вам нужно только сравнить, совпадают ли их адреса в памяти, а не сравнивать содержимое.
- Быстрый поиск в словаре: если ключи поиска интернированы, сравнение может быть выполнено путем сравнения указателя вместо сравнения строк, которое работает по тому же принципу, что и предыдущий пункт.
Недостатки интернирования строк
Однако у интернирующих строк есть некоторые недостатки, которые следует учитывать перед использованием:
- Стоимость памяти: в случае, если ваша программа имеет большое количество строк с разными значениями и относительно меньшее количество сравнений в целом, потому что сама интернированная таблица потребляет память. Это означает, что вы хотите интернировать строки, если у вас относительно мало строк и много сравнений между ними.
- Затраты времени: вызов функции intern() стоит дорого, так как он должен управлять интернированной таблицей.
- Многопоточные среды: интернированная память (таблица) — это глобальный ресурс в многопоточной среде, синхронизацию которой необходимо изменить. Эта проверка может потребоваться только при доступе к интернированной таблице, т. Е. При создании новой строки, но это может быть дорогостоящим.
Заключение
Используя интернирование строк, вы гарантируете, что будет создан только один объект, даже если вы определите несколько строк с одинаковым содержимым. Однако вы должны помнить о балансе между преимуществами и недостатками интернирования строк и использовать его только тогда, когда вы думаете, что ваша программа может принести пользу.
Всегда не забывайте добавлять комментарии или документацию, если вы используете интернирование строк, чтобы другие члены команды знали, как работать со строками в программе.
Хотя результаты могут различаться в зависимости от реализации вашего интерпретатора Python, а также от среды, в которой вы запускаете свой код, вам определенно следует поэкспериментировать с intern() чтобы вам было комфортно с ней работать. Эта концепция может помочь вам улучшить дизайн и производительность вашего кода. Это также может помочь вам на следующем собеседовании.
Интернирование струн — String interning
В информатике интернирование строк — это метод хранения только одной копии каждого отдельного строкового значения, которое должно быть неизменным . Интернирование строк делает некоторые задачи обработки строк более эффективными по времени или пространству за счет того, что для создания или интернирования строки требуется больше времени. Отдельные значения хранятся в внутреннем пуле строк .
Единственная копия каждой строки называется ее внутренней и обычно просматривается методом класса строки, например String.intern () в Java . Все строки констант времени компиляции в Java автоматически интернируются с использованием этого метода.
Интернирование строк поддерживается некоторыми современными объектно-ориентированными языками программирования , включая Java, Python , PHP (начиная с версии 5.4), Lua , Ruby (с его символами), Julia и .NET . Lisp , Scheme и Smalltalk относятся к языкам с символьными типами, которые в основном представляют собой интернированные строки. Библиотека Standard ML of New Jersey содержит атомный тип, который выполняет то же самое. Селекторы Objective-C , которые в основном используются как имена методов, представляют собой интернированные строки.
Могут быть интернированы объекты, отличные от строк. Например, в Java, когда примитивные значения в штучной упаковке в объект обертки , определенные значения (любой boolean , любой byte , любой char от 0 до 127, и любой short или int от -128 до 127) интернированы, и любые два преобразования бокса одного из них значения гарантированно приведут к одному и тому же объекту.
СОДЕРЖАНИЕ
История
Lisp ввел понятие интернированных строк для своих символов . Исторически структура данных, используемая в качестве внутреннего пула строк, называлась облистом (когда он был реализован как связанный список) или obarray (когда он был реализован как массив).
Современные диалекты Лиспа обычно различают символы от строк; интернирование данной строки возвращает существующий символ или создает новый, имя которого совпадает с этой строкой. Символы часто имеют дополнительные свойства, которых нет у строк (например, хранение связанных значений или пространство имен): различие также полезно для предотвращения случайного сравнения интернированной строки с необязательно интернированной строкой, что может привести к периодическим сбоям в зависимости от шаблоны использования.
Мотивация
Интернирование строк ускоряет сравнение строк, которое иногда является узким местом производительности в приложениях (например, компиляторах и средах выполнения динамических языков программирования ), которые в значительной степени полагаются на ассоциативные массивы со строковыми ключами для поиска атрибутов и методов объекта. Без интернирования сравнение двух разных строк может включать изучение каждого символа обеих. Это происходит медленно по нескольким причинам: длина строк по определению составляет O (n) ; обычно требуется чтение из нескольких областей памяти , что требует времени; а операции чтения заполняют кеш процессора, а это означает, что для других нужд остается меньше кеша. С интернированными строками после первоначальной интернированной операции достаточно простого теста на идентичность объекта ; это обычно реализуется как проверка на равенство указателей, обычно это всего лишь одна машинная инструкция без обращения к памяти вообще.
Интернирование строк также снижает использование памяти, если имеется много экземпляров одного и того же строкового значения; например, он читается из сети или из хранилища . Такие строки могут включать в себя магические числа или информацию о сетевом протоколе . Например, синтаксические анализаторы XML могут вставлять имена тегов и атрибутов для экономии памяти. Сетевая передача объектов через потоки объектов сериализации Java RMI может передавать строки, которые интернированы более эффективно, поскольку дескриптор объекта String используется вместо повторяющихся объектов при сериализации.
Проблемы
Многопоточность
Одним из источников недостатков является то, что интернирование строк может быть проблематичным при смешивании с многопоточностью . Во многих системах требуется, чтобы стажеры по строкам были глобальными для всех потоков в адресном пространстве (или в любых контекстах, которые могут совместно использовать указатели), поэтому внутренний пул (ы) — это глобальные ресурсы, которые должны быть синхронизированы для безопасного одновременного доступа. Хотя это влияет только на создание строк (где внутренний пул должен быть проверен и при необходимости изменен), а блокировка с двойной проверкой может использоваться на платформах, где это безопасная оптимизация, необходимость во взаимном исключении при изменении внутреннего пула может быть дорогостоящим. .
Конкуренцию можно также уменьшить, разделив пространство строк на несколько пулов, которые можно синхронизировать независимо друг от друга.
Восстановление неиспользованных интернированных строк
Многие реализации интернированных строк не пытаются вернуть (вручную или иным образом) строки, которые больше не используются. Для приложений, в которых количество интернированных строк невелико или фиксировано, или которые недолговечны, потеря системных ресурсов может быть допустимой. Но для систем с длительным сроком эксплуатации, в которых во время выполнения создается большое количество стажеров-строк, может возникнуть необходимость вернуть неиспользованных стажеров. Эта задача может быть обработана сборщиком мусора , хотя для правильной работы слабые ссылки на строковых стажеров должны храниться в внутреннем пуле.