Что такое interning и как им пользоваться

VladD

Что такое interning? Для чего оно применяется? Когда стоит его применять и какие возможны подводные камни?

1 ответ

VladD

Interning — это метод хранения лишь одной копии из многих одинаковых объектов. Применяется в C# и Java к строкам, а также (в Java) к небольшим числам.

Рассмотрим на примере строк. Когда вы говорите string.Intern(s) в C# или s.intern() в Java для строки s, вы получаете строку с таким же содержимым, но возвращённая строка гарантировано одна и та же (то есть, один и тот же объект), если вы запрашиваете интернированную строку с одним и тем же содержимым. Также, строковые константы автоматически интернируются.

Однако, строки полученные другим путём, например, через StringBuilder или конкатенацию, не будут интернированы, по крайней мере в текущей версии языков. (Впрочем, оптимизатор может соптимизировать конкатенацию, если сумеет вычислить аргументы во время компиляции, так что рассчитывать на это не стоит.)

Пример:

// C#
object.ReferenceEquals("123", "123")                             // true
object.ReferenceEquals(string.Intern("12" + "3"), "123")         // true
char[] chars = new[] { '1', '2', '3' };
object.ReferenceEquals(new string(chars), new string(chars))     // false
object.ReferenceEquals(new string(chars), "123")                 // false
object.ReferenceEquals(string.Intern(new string(chars)), "123")  // true

// Java
"123" == "123"                          // true
("12" + "3").intern() == "123"          // true
new String("123") == new String("123")  // false
new String("123") == "123"              // false
new String("123").intern() == "123"     // true

Это значит, что интернированные объекты можно сравнивать через ReferenceEquals (C#) / == (Java).

Когда вызывается метод Intern()/intern(), рантайм-библиотека просматривает пул интернированных объектов в поисках данного или равного ему. Если такой объект находится, он возвращается, если нет, данный объект интернируется и возвращается.

Для чего можно пользоваться этим? Например, можно уменьшить расход памяти программы, если в ней используется большое количество строк, среди которых много дубликатов. Например, у вас есть огромный XML-файл, состоящий из почти одинаковых записей. Или огромный текст программы на каком-нибудь языке программирования. Тогда в некоторых случаях можно уменьшить потребление памяти путём интернирования строк: например, все экземпляры while будут одним и тем же объектом.

Внимание! Сама по себе считанная из файла строка не интернируется, даже если она и равна какой-то интернированной строке.

Учтите, однако, что однажды интернированую строку нельзя «деинтернировать», и она будет занимать память программы даже когда больше не будет вам нужна. Поэтому имейте в виду, что интернирование строк может оказать и негативный эффект на расход памяти программой!

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

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

Ещё одним преимуществом интернированных строк является то, что их можно быстрее сравнивать. Например, если вы разбираете программный текст, и все ключевые слова интернированы, вы можете сравнивать их как объекты (что, разумеется, намного скорее).

В .NET вы можете управлять тем, будет ли применяться автоматическое интернирование строковых констант на уровне сборок (assembly). По умолчанию строковые константы, как было сказано выше, интернируются, но вы можете запретить это, указав атрибут CompilationRelaxations.NoStringInterning.

В Java кроме строк интернируются также и упакованные (boxed) числа. Например, упакованные константы типов Integer и Long в пределах от -128 до 127, Boolean и Byte хранятся в пуле интернированных объектов. Пример:

Integer x = 1;
Integer y = 1;
Integer z = new Integer(1);

x == y    // true
y == z    // false

licensed under cc by-sa 3.0 with attribution.