String. Intern(String) Метод
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Извлекает системную ссылку на указанный объект String.
public: static System::String ^ Intern(System::String ^ str);
public static string Intern (string str);
static member Intern : string -> string
Public Shared Function Intern (str As String) As String
Параметры
Строка для поиска в пуле интернирования.
Возвращаемое значение
Системная ссылка на значение str , если оно уже интернировано; в противном случае возвращается новая ссылка на строку со значением str .
Исключения
str имеет значение null .
Примеры
В следующем примере используются три строки, равные по значению, чтобы определить, равны ли только что созданная строка и интернированная строка.
// Sample for String::Intern(String) using namespace System; using namespace System::Text; int main() < String^ s1 = "MyTest"; String^ s2 = (gcnew StringBuilder)->Append( "My" )->Append( "Test" )->ToString(); String^ s3 = String::Intern( s2 ); Console::WriteLine( "s1 == ''", s1 ); Console::WriteLine( "s2 == ''", s2 ); Console::WriteLine( "s3 == ''", s3 ); Console::WriteLine( "Is s2 the same reference as s1?: ", s2 == s1 ); Console::WriteLine( "Is s3 the same reference as s1?: ", s3 == s1 ); > /* This example produces the following results: s1 == 'MyTest' s2 == 'MyTest' s3 == 'MyTest' Is s2 the same reference as s1?: False Is s3 the same reference as s1?: True */
// Sample for String.Intern(String) using System; using System.Text; class Sample < public static void Main() < string s1 = "MyTest"; string s2 = new StringBuilder().Append("My").Append("Test").ToString(); string s3 = String.Intern(s2); Console.WriteLine($"s1 == "); Console.WriteLine($"s2 == "); Console.WriteLine($"s3 == "); Console.WriteLine($"Is s2 the same reference as s1?: "); Console.WriteLine($"Is s3 the same reference as s1?: "); > > /* This example produces the following results: s1 == MyTest s2 == MyTest s3 == MyTest Is s2 the same reference as s1?: False Is s3 the same reference as s1?: True */
// Sample for String.Intern(String) open System open System.Text let s1 = "MyTest" let s2 = StringBuilder().Append("My").Append("Test").ToString() let s3 = String.Intern s2 printfn $"s1 = " printfn $"s2 = " printfn $"s3 = " printfn $"Is s2 the same reference as s1?: obj = s1 :> obj>" printfn $"Is s3 the same reference as s1?: obj = s1 :> obj>" (* This example produces the following results: s1 = MyTest s2 = MyTest s3 = MyTest Is s2 the same reference as s1?: False Is s3 the same reference as s1?: True *)
Imports System.Text Class Sample Public Shared Sub Main() Dim s1 As String = "MyTest" Dim s2 As String = New StringBuilder().Append("My").Append("Test").ToString() Dim s3 As String = String.Intern(s2) Console.WriteLine($"s1 = ") Console.WriteLine($"s2 = ") Console.WriteLine($"s3 = ") Console.WriteLine($"Is s2 the same reference as s1?: ") Console.WriteLine($"Is s3 the same reference as s1?: ") End Sub End Class ' 's1 = MyTest 's2 = MyTest 's3 = MyTest 'Is s2 the same reference as s1?: False 'Is s3 the same reference as s1?: True '
Комментарии
Общеязыковая среда выполнения сохраняет хранилище строк, поддерживая таблицу, называемую пулом стажера, которая содержит одну ссылку на каждую уникальную литеральную строку, объявленную или созданную программным способом в программе. Следовательно, экземпляр строки литерала с определенным значением существует в системе только один раз.
Например, если назначить одну и ту же литеральную строку нескольким переменным, среда выполнения извлекает одну и ту же ссылку на литеральную строку из пула стажера и назначает ее каждой переменной.
Метод Intern использует пул стажера для поиска строки, равной str значению . Если такая строка существует, возвращается ее ссылка в пуле стажера. Если строка не существует, ссылка на str добавляется в пул стажера, а затем возвращается эта ссылка.
В следующем примере строка s1, которая имеет значение «MyTest», уже интернирована, так как это литерал в программе. Класс System.Text.StringBuilder создает новый строковый объект, имеющий то же значение, что и s1. Ссылка на такую строку назначается s2. Метод Intern ищет строку, которая имеет то же значение, что и s2. Так как такая строка существует, метод возвращает ту же ссылку, которая назначена s1. Затем эта ссылка назначается s3. Ссылки s1 и s2 сравнивают неравные, так как они ссылаются на разные объекты; ссылки s1 и s3 сравниваются равными, так как они ссылаются на одну и ту же строку.
string s1 = "MyTest"; string s2 = new StringBuilder().Append("My").Append("Test").ToString(); string s3 = String.Intern(s2); Console.WriteLine((Object)s2==(Object)s1); // Different references. Console.WriteLine((Object)s3==(Object)s1); // The same reference.
let s1 = "MyTest" let s2 = StringBuilder().Append("My").Append("Test").ToString() let s3 = String.Intern s2 printfn $" obj = s1 :> obj>" // Different references. printfn $" obj = s1 :> obj>" // The same reference.
Dim s1 As String = "MyTest" Dim s2 As String = New StringBuilder().Append("My").Append("Test").ToString() Dim s3 As String = String.Intern(s2) Console.WriteLine(CObj(s2) Is CObj(s1)) ' Different references. Console.WriteLine(CObj(s3) Is CObj(s1)) ' The same reference.
Сравните этот метод с методом IsInterned .
Рекомендации по версиям
В платформа .NET Framework 3.5 с пакетом обновления 1 (SP1Intern) метод возвращается к поведению в платформа .NET Framework 1.0 и 1.1 в отношении интернирования пустой строки. В следующем примере переменной str1 назначается ссылка на Empty, а переменной str2 назначается ссылка на Empty , которая возвращается путем вызова Intern метода после преобразования StringBuilder объекта, значение которого является Empty строкой. Затем ссылки, содержащиеся в str1 и str2 , сравниваются на равенство.
string str1 = String.Empty; string str2 = String.Empty; StringBuilder sb = new StringBuilder().Append(String.Empty); str2 = String.Intern(sb.ToString()); if((object)str1==(object)str2) Console.WriteLine("The strings are equal."); else Console.WriteLine("The strings are not equal.");
let str1 = String.Empty let str2 = String.Empty let sb = StringBuilder().Append String.Empty let str3 = String.Intern(string sb) if (str1 :> obj) = (str3 :> obj) then printfn "The strings are equal." else printfn "The strings are not equal."
Dim str1 As String = String.Empty Dim str2 As String = String.Empty Dim sb As StringBuilder = New StringBuilder().Append(String.Empty) str2 = String.Intern(sb.ToString()) If CObj(str1) Is CObj(str2) Then Console.WriteLine("The strings are equal.") Else Console.WriteLine("The strings are not equal.") End If
В платформа .NET Framework 2.0 с пакетом обновления 1 (SP1) и платформа .NET Framework 3.0 и str1 str2 не равны. Во всех остальных версиях str1 и str2 равны.
Вопросы производительности
Если вы пытаетесь уменьшить общий объем памяти, выделяемой приложением, помните, что интернирование строки имеет два нежелательных побочных эффекта. Во-первых, память, выделенная для интернированных String объектов, вряд ли будет освобождена до завершения работы среды CLR. Причина в том, что ссылка среды CLR на интернированный String объект может сохраняться после завершения работы приложения или даже домена приложения. Во-вторых, для интернирования строки необходимо сначала создать строку. Память, используемая объектом, по-прежнему String должна быть выделена, даже если в конечном итоге память будет собрана мусором.
Член перечисления помечает CompilationRelaxations.NoStringInterning сборку как не требующую интернирования строкового литерала. Можно применить к NoStringInterning сборке с помощью атрибута CompilationRelaxationsAttribute . Кроме того, при использовании Ngen.exe (генератор образов в машинном коде) для компиляции сборки до времени выполнения строки не интернируются между модулями.
Применяется к
См. также раздел
15.14. Java – Метод intern()
Метод intern() – возвращает каноническое представление для строкового объекта. Отсюда следует, что для любых двух строк s и t, s.intern() == t.intern(), истинно тогда и только тогда, когда s.equals(t) имеет значение true.
Синтаксис
public String intern()
Параметры
Подробная информация о параметрах:
- Это метод по умолчанию не принимает никаких параметров.
Возвращаемое значение
- В Java intern() возвращает каноническое представление для строкового объекта.
Пример
import java.io.*; public class Test < public static void main(String args[])< String Str1 = new String("Добро пожаловать на ProgLang.su"); String Str2 = new String("ДОБРО ПОЖАЛОВАТЬ НА PROGLANG.SU"); System.out.print("Каноническое представление Str1: " ); System.out.println(Str1.intern()); System.out.print("Каноническое представление Str2: " ); System.out.println(Str2.intern()); >>
Получим следующий результат:
Каноническое представление Str1: Добро пожаловать на ProgLang.su Каноническое представление Str2: ДОБРО ПОЖАЛОВАТЬ НА PROGLANG.SU
Оглавление
- 1. Java – Самоучитель для начинающих
- 2. Java – Обзор языка
- 3. Java – Установка и настройка
- 4. Java – Синтаксис
- 5. Java – Классы и объекты
- 6. Java – Конструкторы
- 7. Java – Типы данных и литералы
- 8. Java – Типы переменных
- 9. Java – Модификаторы
- 10. Java – Операторы
- 11. Java – Циклы и операторы цикла
- 11.1. Java – Цикл while
- 11.2. Java – Цикл for
- 11.3. Java – Улучшенный цикл for
- 11.4. Java – Цикл do..while
- 11.5. Java – Оператор break
- 11.6. Java – Оператор continue
- 12. Java – Операторы принятия решений
- 12.1. Java – Оператор if
- 12.2. Java – Оператор if..else
- 12.3. Java – Вложенный оператор if
- 12.4. Java – Оператор switch..case
- 12.5. Java – Условный оператор (? 🙂
- 13. Java – Числа
- 13.1. Java – Методы byteValue(), shortValue(), intValue(), longValue(), floatValue(), doubleValue()
- 13.2. Java – Метод compareTo()
- 13.3. Java – Метод equals()
- 13.4. Java – Метод valueOf()
- 13.5. Java – Метод toString()
- 13.6. Java – Метод parseInt()
- 13.7. Java – Метод Math.abs()
- 13.8. Java – Метод Math.ceil()
- 13.9. Java – Метод Math.floor()
- 13.10. Java – Метод Math.rint()
- 13.11. Java – Метод Math.round()
- 13.12. Java – Метод Math.min()
- 13.13. Java – Метод Math.max()
- 13.14. Java – Метод Math.exp()
- 13.15. Java – Метод Math.log()
- 13.16. Java – Метод Math.pow()
- 13.17. Java – Метод Math.sqrt()
- 13.18. Java – Метод Math.sin()
- 13.19. Java – Метод Math.cos()
- 13.20. Java – Метод Math.tan()
- 13.21. Java – Метод Math.asin()
- 13.22. Java – Метод Math.acos()
- 13.23. Java – Метод Math.atan()
- 13.24. Java – Метод Math.atan2()
- 13.25. Java – Метод Math.toDegrees()
- 13.26. Java – Метод Math.toRadians()
- 13.27. Java – Метод Math.random()
- 14. Java – Символы
- 14.1. Java – Метод Character.isLetter()
- 14.2. Java – Метод Character.isDigit()
- 14.3. Java – Метод Character.isWhitespace()
- 14.4. Java – Метод Character.isUpperCase()
- 14.5. Java – Метод Character.isLowerCase()
- 14.6. Java – Метод Character.toUpperCase()
- 14.7. Java – Метод Character.toLowerCase()
- 14.8. Java – Метод Character.toString()
- 15. Java – Строки
- 15.1. Java – Метод charAt()
- 15.2. Java – Метод compareTo()
- 15.3. Java – Метод compareToIgnoreCase()
- 15.4. Java – Метод concat()
- 15.5. Java – Метод contentEquals()
- 15.6. Java – Метод copyValueOf()
- 15.7. Java – Метод endsWith()
- 15.8. Java – Метод equals()
- 15.9. Java – Метод equalsIgnoreCase()
- 15.10. Java – Метод getBytes()
- 15.11. Java – Метод getChars()
- 15.12. Java – Метод hashCode()
- 15.13. Java – Метод indexOf()
- 15.14. Java – Метод intern()
- 15.15. Java – Метод lastIndexOf()
- 15.16. Java – Метод length()
- 15.17. Java – Метод matches()
- 15.18. Java – Метод regionMatches()
- 15.19. Java – Метод replace()
- 15.20. Java – Метод replaceAll()
- 15.21. Java – Метод replaceFirst()
- 15.22. Java – Метод split()
- 15.23. Java – Метод startsWith()
- 15.24. Java – Метод subSequence()
- 15.25. Java – Метод substring()
- 15.26. Java – Метод toCharArray()
- 15.27. Java – Метод toLowerCase()
- 15.28. Java – Метод toString()
- 15.29. Java – Метод toUpperCase()
- 15.30. Java – Метод trim()
- 15.31. Java – Метод valueOf()
- 15.32. Java – Классы StringBuilder и StringBuffer
- 15.32.1. Java – Метод append()
- 15.32.2. Java – Метод reverse()
- 15.32.3. Java – Метод delete()
- 15.32.4. Java – Метод insert()
- 15.32.5. Java – Метод replace()
- 16. Java – Массивы
- 17. Java – Дата и время
- 18. Java – Регулярные выражения
- 19. Java – Методы
- 20. Java – Потоки ввода/вывода, файлы и каталоги
- 20.1. Java – Класс ByteArrayInputStream
- 20.2. Java – Класс DataInputStream
- 20.3. Java – Класс ByteArrayOutputStream
- 20.4. Java – Класс DataOutputStream
- 20.5. Java – Класс File
- 20.6. Java – Класс FileReader
- 20.7. Java – Класс FileWriter
- 21. Java – Исключения
- 21.1. Java – Встроенные исключения
- 22. Java – Вложенные и внутренние классы
- 23. Java – Наследование
- 24. Java – Переопределение
- 25. Java – Полиморфизм
- 26. Java – Абстракция
- 27. Java – Инкапсуляция
- 28. Java – Интерфейсы
- 29. Java – Пакеты
- 30. Java – Структуры данных
- 30.1. Java – Интерфейс Enumeration
- 30.2. Java – Класс BitSet
- 30.3. Java – Класс Vector
- 30.4. Java – Класс Stack
- 30.5. Java – Класс Dictionary
- 30.6. Java – Класс Hashtable
- 30.7. Java – Класс Properties
- 31. Java – Коллекции
- 31.1. Java – Интерфейс Collection
- 31.2. Java – Интерфейс List
- 31.3. Java – Интерфейс Set
- 31.4. Java – Интерфейс SortedSet
- 31.5. Java – Интерфейс Map
- 31.6. Java – Интерфейс Map.Entry
- 31.7. Java – Интерфейс SortedMap
- 31.8. Java – Класс LinkedList
- 31.9. Java – Класс ArrayList
- 31.10. Java – Класс HashSet
- 31.11. Java – Класс LinkedHashSet
- 31.12. Java – Класс TreeSet
- 31.13. Java – Класс HashMap
- 31.14. Java – Класс TreeMap
- 31.15. Java – Класс WeakHashMap
- 31.16. Java – Класс LinkedHashMap
- 31.17. Java – Класс IdentityHashMap
- 31.18. Java – Алгоритмы Collection
- 31.19. Java – Iterator и ListIterator
- 31.20. Java – Comparator
- 32. Java – Дженерики
- 33. Java – Сериализация
- 34. Java – Сеть
- 34.1. Java – Обработка URL
- 35. Java – Отправка Email
- 36. Java – Многопоточность
- 36.1. Java – Синхронизация потоков
- 36.2. Java – Межпоточная связь
- 36.3. Java – Взаимная блокировка потоков
- 36.4. Java – Управление потоками
- 37. Java – Основы работы с апплетами
- 38. Java – Javadoc
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() — точки входа консольного приложения:
using System; class Program < static void Main(string[] args) < string a = "hello world"; string b = a; a = "hello"; Console.WriteLine(", ", a, b); Console.WriteLine(a == b); Console.WriteLine(object.ReferenceEquals(a, b)); > >
В этом коде не должно быть никаких сюрпризов. Программа выводит три строки на консоль (помните, если вы работаете в Visual Studio, используйте Ctrl-F5, чтобы запустить программу вне отладчика; также в программу будет добавлено «Press any key . », что-бы предотвратить закрытие окна консоли):
hello, hello world
False
False
Первый WriteLine() выводит две строки. Второй сравнивает их с помощью оператора равенства ==, который возвращает False, потому что строки не совпадают. И последний сравнивает их, чтобы увидеть не ссылаются ли обе переменные на один и тот же объект String. Поскольку это не так, метод отображает значение False.
Затем добавьте эти две строки в конец метода Main():
Console.WriteLine((a + " world") == b); Console.WriteLine(object.ReferenceEquals((a + " world"), b));
И опять вы получите довольно очевидный ответ. Оператор равенства возвращает True, так как обе строки равны. Но когда вы использовали конкатенацию строк «Hello» и «world», оператор + объединяет их и возвращает новый экземпляр System.String. Именно поэтому object.ReferenceEquals() вполне резонно возвращает False. Метод ReferenceEquals() возвращает True только в том случае, если оба аргумента ссылаются на один и тот же объект.
Такой способ позволяет нормально работать с объектами. Два разных объекта могут иметь одинаковые значения. Такое поведение является вполне практичным и предсказуемым. Если вы создаете два объекта “дом” и установите всем их свойствам одинаковые значения, вы будете иметь два одинаковых объекта типа “дом”, но это будут различные объекты.
Это все еще кажется немного запутанным? Если так, то я определенно рекомендую обратить внимание на несколько первых глав “Head First C#”, которые дадут вам представление о написании программ, отладке, и использование объектов и классов. Вы можете скачать их как бесплатные вырезки из этой книги.
Итак, пока мы работаем со строками — все прекрасно. Но как только мы начинаем играться ссылками на строки, все становится немного странным.
Что-то с этой ссылкой не так .
Создайте новое консольное приложение. Код ниже для него. Но, перед компиляцией и выполнением, внимательно посмотрите на код. Попробуйте угадать, что он отобразит в консоли?
using System; class Program < static void Main(string[] args) < string hello = "hello"; string helloWorld = "hello world"; string helloWorld2 = hello + " world"; Console.WriteLine(", : , ", helloWorld, helloWorld2, helloWorld == helloWorld2, object.ReferenceEquals(helloWorld, helloWorld2)); > >
Теперь запустите программу. Вот то, что она отобразит в консоли:
hello world, hello world: True, False
И так, это именно то, что мы ожидали. В объектах helloWorld и helloWorld2 строки содержат “Hello world», так что они равны, но ссылки разные.
Теперь добавьте в нижней части вашей программы этот код:
helloWorld2 = "hello world"; Console.WriteLine(", : , ", helloWorld, helloWorld2, helloWorld == helloWorld2, object.ReferenceEquals(helloWorld, helloWorld2));
Запустите его. На этот раз код отобразит в консоли следующую строку:
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() работает очень простым способом. Вы передадите ему в качестве аргумента строку. Если эта строка уже находится в пуле интернирования, метод возвращает ссылку на эту строку. Если её еще не нет, он добавляет строку в пул и возвращает на неё ссылку. Вот пример:
Console.WriteLine(object.ReferenceEquals( String.Intern(helloWorld), String.Intern(helloWorld2)));
Этот код будет отображать True, даже если HelloWorld и HelloWorld2 ссылки на два разных строковых объекта, потому что они оба содержат строку «Hello World».
Остановитесь на минутку. Стоит ещё немного поразбираться с String.Intern() потому, что иногда метод дает немного нелогичные на первый взгляд результаты. Вот пример такого поведения:
string a = new string(new char[] ); object o = String.Copy(a); Console.WriteLine(object.ReferenceEquals(o, a)); String.Intern(o.ToString()); Console.WriteLine(object.ReferenceEquals(o, String.Intern(a)));
Выполнение кода выведет две строки на консоль. Первый метод WriteLine() покажет значение False, и это понятно, так как метод String.Copy() создает новую копию строки и возвращает ссылку на новый объект. Но почему выполнив вначале String.Intern(о.ToString()) затем String.Intern(a) вернёт ссылку на о? Остановитесь на минутку, чтобы подумать об этом. Это становится еще более нелогичным, если вы добавите еще три строки:
object o2 = String.Copy(a); String.Intern(o2.ToString()); Console.WriteLine(object.ReferenceEquals(o2, 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-коалесцирующий оператор — ??. К примеру написав:
string o = String.IsInterned(str) ?? "not interned";
Теперь в переменную о вернется результат IsInterned() если он не нулевой, или строка «not interned», если строки нет в пуле интернирования.
Если этого не сделать, то метод Console.WriteLine() будет выводить пустые строки (что делает этот метод, когда сталкивается null).
Вот простой пример того, как String.IsInterned() работает:
string s = new string(new char[] ); Console.WriteLine(String.IsInterned(s) ?? "not interned"); String.Intern(s); Console.WriteLine(String.IsInterned(s) ?? "not interned"); Console.WriteLine(object.ReferenceEquals( String.IsInterned(new string(new char[] < 'x', 'y', 'z' >)), s));
Первый WriteLine() оператор отобразит в консоли «not interned», потому что «xyz» еще нет в пуле интернирования. Второй WriteLine() оператор печатает «xyz» потому, что пул интернирования уже содержит «xyz». И третий WriteLine () выведет True, так как объект s указывает на объект, добавленный в пул интернирования.
Литералы интернируются автоматически
Добавив всего одну строку в конец метода и запустив программу снова:
Сonsole.WriteLine(object.ReferenceEquals("xyz", с));
произойдет что-то совсем неожиданное!
Программа никогда не отобразит «not interned», а последние два метода WriteLine() покажут False! Если мы закомментируем последнюю строку, то программа действует именно так, как вы ожидали. Почему?! Как добавив код в конце программы, поменялось поведение программы кода над ним? Это очень, очень странно!
Это кажется действительно странным в первый раз, когда вы сталкиваетесь с этим, но в этом действительно есть смысл. Причина изменения поведения всей программы в том, что код содержит литерал «xyz». А когда вы добавляете литерал в вашу программу, CLR автоматически добавляет его в пул интернирования ещё до начала выполнения программы. Комментируете эту строку, вы убираете литерал из программы и пул интернирования уже не будет содержать строку «xyz».
Понимая, что «xyz» уже находится в пуле интернирования при запуске программы, так как эта строка в виде литерала появилась в коде, то сразу стаёт понятным такое изменение в поведении программы. String.IsInterned(s) больше не возвращает null. Вместо этого, он возвращает ссылку на литерал «xyz», что также объясняет, почему ReferenceEquals() возвращает False. Это происходит из за того, что строка s никогда не будет добавлена в пул интернирования («xyz» уже в пуле, указывая на другой объект).
Компилятор умнее, чем вы думаете!
Измените последнюю строку кода на эту:
Console.WriteLine( object.ReferenceEquals("x" + "y" + "z", s));
Запустите программу. Она работает точно так же, как если бы вы использовали литерал «xyz»! Неужели + не оператор? Разве это не метод, который запускается на выполнение по CLR во время выполнения? Если это так, то должен быть код, который предотвратит интернирование литерала «xyz».
В самом деле так и произойдёт если вы замените «х» + «у» + «z» на String.Format(«», ‘x’, ‘y’, ‘z’). Обе строчки кода возвращают «xyz». Почему же при помощи оператора + для конкатенации получаем поведение, как если бы вы использовали литерал «xyz», хотя в тоже самое время как String.Format() выполняется во время выполнения?
Самый простой способ ответить на этот вопрос — это увидеть то, что на самом деле получаем при компиляции кода «x» + «у» + «z» .
using System; class Program < public static void Main() < Console.WriteLine("x" + "y" + "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».
Если у вас .NET Framework 3.5 или более ранней версии
Если у вас .NET Framework 3.5 или более ранней версии, возможно, потребуется поискать ildasm.exe в соседних папках. Запустите окно проводника и перейдите в папку Program Files. Как правило нужная пограмма находится в папке «Microsoft SDKs\Windows\vX.X\bin». Кроме того вы можете запустить командную строку из «Visual Studio Command Prompt» которая находится в меню Пуск, после чего набрать «ILDASM» для его запуска.
Вот так, выглядит 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 добавляет его в пул интернирования при запуске программы.
Материал данной статьи должен дать вам хорошее представление о интернировании строк в C# и .NET. В принципе этого даже больше чем нужно для понимания работы интернирования строк. Если вы заинтересованы в получении дополнительной информации, хорошим плацдармом является раздел «Performance Considerations» на страницах MSDN о String.Intern.
P.S.: Спасибо команде за усердную вычитку и объективную критику перевода.
- Интернирование строк
- .NET framework
- String.Intern
- ildasm
Все о String.intern()
Думаю, что многие Java-разработчики знают или хотя бы слышали о методе String.intern() . Но далеко не каждый использует его в своих приложениях или представляет в каких случаях он полезен и полезен ли вообще. Так было и со мной до тех пор пока я не столкнулся с этим методом в одном из проектов. В тот момент я захотел узнать смысл и особенности его использования и набрел на одну весьма интересную статью ведущего разработчика Yahoo! по имени Ethan Nicholas, переводом которой теперь хочу поделиться с той частью Хабра-сообщества, которая не безразлична к языку Java.
Тем, кто знает об этом методе лишь понаслышке, добро пожаловать под кат.
Строки являются фундаментальной частью любого современного языка программирования и так же важны, как и числа. Поэтому можно предположить, что Java программисты должны иметь свое твердое представление о них, но к сожалению, это не всегда так.
Сегодня я просматривал исходный код Xerces (XML-парсер, включенных в Java) и наткнулся на строку, которая меня очень удивила:
com.sun.org.apache.xerces.internal.impl.XMLScanner:395
protected final static String fVersionSymbol = «version».intern();
Далее я нашел еще несколько строк, определенных как эта, и каждая из них была интернирована. Так что же такое intern() ? Ну, как вы, несомненно, знаете, существует два различных способа для сравнения объектов в Java. Вы можете использовать оператор == , или же вы можете использовать метод equals() . Оператор == сравнивает ссылаются ли две ссылки на один и тот же объект, в то время как equals() сравнивает содержат ли два объекта одни и те же данные.
Одним из первых уроков, который вы усваиваете при изучении Java является то, что обычно для сравнения двух строк вы должны использовать equals() , а не == . Если сравнить, скажем, new String(«Hello») == new String(«Hello») , то в результате получится false , потому что это два разных экземпляра класса. Если же вы используете equals() , то получите true , как и ожидаете. К сожалению, equals() может оказаться довольно медленным, поскольку он выполняет посимвольное сравнение строк.
Т.к. оператор == проверяет идентичность (identity), все, что он должен сделать — это сравнить два указателя, и, очевидно, это будет гораздо быстрее, чем equals() . Так что если вы собираетесь сравнивать одни и те же строки многократно, вы можете получить значительное преимущество в производительности за счет использования проверки идентичности объектов вместо сравнения символов.
1) Создать множество (hash set) строк
2) Проверить, что строка (как последовательность символов), с которой вы имеете дело, уже в множестве
3) Если да, то использовать строку из множества
4) В противном случае, добавить эту строку в множество и затем использовать ее
При использовании этого алгоритма гарантируется, что если две строки являются идентичными последовательностями символов, они являются одним экземпляром класса. Это означает, что вы можете спокойно сравнивать строки, используя == вместо equals() , получая при этом значительные преимущества производительности при многократно повторяющихся сравнениях.
К счастью Java уже включает в себя реализацию этого алгоритма. Это метод intern() в классе java.lang.String . Выражение new String(«Hello»).intern() == new String(«Hello»).intern() возвращает true , в то время как без использования intern() возвращается false .
Так почему же я так удивился, увидев
protected final static String fVersionSymbol = «version».intern();
в исходном коде Xerces? Очевидно, что эта строка будет использоваться для многократных сравнений. Имеет ли смысл интернировать ее?
Конечно, имеет. Вот почему Java уже это делает. Все строки-константы, которые встречаются в классе автоматически интернированы. Сюда входят как собственные константы (например, приведенная выше строка «version» ), так и другие строки, которые являются частью формата файла класса — имена классов, сигнатуры методов и так далее. Это распространяется даже на выражения: «Hel» + «lo» обрабатывается javac точно так же, как «Hello» , поэтому «Hel» + «lo» == «Hello» возвращает true .
Таким образом, результатом вызова intern() для строки-константы типа «version» , по определению, будет точно тот же объект, который вы объявили. Другими словами «version» == «version».intern() всегда истинно. Вам нужно интернировать строки тогда, когда они не являются константами, и вы хотите иметь возможность быстро сравнить их с другими интернированными строками.
Также при интернировании строк можно получить преимущество в использовании памяти, т.к. вы храните в ней лишь один экземпляр последовательности символов строки, независимо от того, сколько раз вы на эту последовательность ссылаетесь. Это основная причина того, почему строковые константы файла класса интернированы: подумайте о том, сколько классов ссылаются, например, на java.lang.Object . Имя класса java.lang.Object должно появиться в каждом из этих классов, но, благодаря магии intern() , оно появляется в памяти лишь в одном экземпляре.
Вывод? intern() является полезным методом и может сделать жизнь легче — но убедитесь, что вы используете его должным образом.
От переводчика
Прошу простить за то, что пару раз исказил исходный текст, чтобы сделать его более понятным (как мне казалось).
Большое спасибо хабраюзеру nolled, который пригласил меня в Хабрасообщество.
Update
Думаю, что следующая информация, которую я узнал из других источников будет здесь не лишней:
1. Пул строк хранится в области «Perm Gen», которая зарезервирована для non-user объектов JVM (классы и пр). Если этого не учитывать, вы можете неожиданно получить OutOfMemory Error.
2. Интернированные строки не хранятся вечно. Строки, на которых нет ссылок, также удаляются сборщиком мусора.
3. В большинстве случаев вы не получите существенного прироста производительности от использования intern() — если сравнение строк не является основной (или очень частой) операцией вашего приложения и сравниваемые строки разные по длине.