совпадение
Java была определена с самого начала из соображений параллелизма. Как часто упоминалось, общие переменные являются проблематичными. Одна вещь может изменить другую за спиной другого потока без того, чтобы этот поток знал об этом.
Существует множество многопоточных ошибок C ++, которые возникли из-за общей строки - когда один модуль думал, что его можно безопасно изменить, когда другой модуль в коде сохранил указатель на него, и ожидал, что он останется прежним.
«Решение» этого заключается в том, что каждый класс создает защитную копию изменяемых объектов, которые ему передаются. Для изменяемых строк это O (n), чтобы сделать копию. Для неизменяемых строк создание копии - это O (1), потому что это не копия, а тот же объект, который не может измениться.
В многопоточной среде неизменные объекты всегда можно безопасно разделить между собой. Это приводит к общему снижению использования памяти и улучшает кеширование памяти.
Безопасность
Часто в качестве аргументов для конструкторов передаются строки - сетевые соединения и протоколы - два, которые легче всего приходят на ум. Возможность изменить это в неопределенное время позже при выполнении может привести к проблемам с безопасностью (функция думала, что она подключалась к одной машине, но была перенаправлена на другую, но все в объекте выглядит так, как будто оно подключено к первой ... это даже та же строка).
Java позволяет использовать отражение - и параметры для этого являются строками. Опасность передачи строки, которая может быть изменена путем к другому методу, который отражает. Это очень плохо.
Ключи к хэшу
Хеш-таблица является одной из наиболее часто используемых структур данных. Ключи к структуре данных очень часто являются строками. Наличие неизменяемых строк означает, что (как указано выше) хеш-таблица не должна делать копию хеш-ключа каждый раз. Если бы строки были изменяемыми, а хеш-таблица этого не делала, можно было бы что-то изменить на расстоянии.
Способ работы объекта в Java заключается в том, что все имеет ключ хеша (доступ к которому осуществляется с помощью метода hashCode ()). Наличие неизменяемой строки означает, что hashCode может быть кэширован. Учитывая, как часто строки используются в качестве ключей для хэша, это обеспечивает значительное повышение производительности (вместо того, чтобы каждый раз пересчитывать хэш-код).
Подстроки
Если String будет неизменным, базовый массив символов, который поддерживает структуру данных, также будет неизменным. Это позволяет выполнять определенные оптимизации substring
метода (они не обязательно выполняются - это также создает возможность некоторых утечек памяти).
Если вы делаете:
String foo = "smiles";
String bar = foo.substring(1,5);
Значение bar
«миля». Тем не менее, как foo
и bar
может быть поддержан тот же массив символов, уменьшая экземпляра более массивов символов или копирование его - просто используя различные начальные и конечные точки в пределах строки.
фу | | (0, 6)
ст
улыбки
^ ^
бар | | (1, 5)
Теперь недостатком этого (утечка памяти) является то, что если бы у вас была строка длиной 1 КБ и была взята подстрока первого и второго символа, она также была бы подкреплена массивом символов 1 КБ. Этот массив останется в памяти, даже если исходная строка, которая имеет значение всего массива символов, была собрана сборщиком мусора.
Это можно увидеть в String из JDK 6b14 (следующий код взят из источника GPL v2 и используется в качестве примера)
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.offset = 0;
this.count = count;
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
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);
}
Обратите внимание, как подстрока использует конструктор String уровня пакета, который не включает в себя копирование массива и будет намного быстрее (за счет возможного хранения некоторых больших массивов - хотя и без дублирования больших массивов).
Обратите внимание, что приведенный выше код предназначен для Java 1.6. Способ реализации конструктора подстроки был изменен в Java 1.7, как описано в документе « Изменения во внутреннем представлении String, сделанном в Java 1.7.0_06» - проблема, связанная
с утечкой памяти, о которой я упоминал выше. Скорее всего, Java не рассматривался как язык с большим количеством манипуляций со строками, поэтому повышение производительности подстроки было хорошей вещью. Теперь, когда огромные XML-документы хранятся в строках, которые никогда не собираются, это становится проблемой ... и, таким образом, происходит переход к использованию String
не того же базового массива с подстрокой, чтобы более крупный массив символов можно было собирать быстрее.
Не злоупотребляйте стеком
Один может передать значение строки вокруг вместо ссылки на незыблемую строку , чтобы избежать проблем с изменчивостью. Однако с большими строками передача этого в стек будет ... оскорбительной для системы (помещать целые XML-документы в виде строк в стек и затем снимать их или продолжать передавать их вместе ...).
Возможность дедупликации
Конечно, это не было первоначальной мотивацией того, почему строки должны быть неизменяемыми, но когда кто-то смотрит на рациональное объяснение того, почему неизменяемые строки - хорошая вещь, это, безусловно, нужно учитывать.
Любой, кто немного работал со строками, знает, что они могут высосать память. Это особенно верно, когда вы делаете такие вещи, как извлечение данных из баз данных, которые остаются на некоторое время. Много раз с этими укусами, они снова и снова становятся одной и той же строкой (по одному разу для каждой строки).
Многие крупные Java-приложения в настоящее время имеют узкое место в памяти. Измерения показали, что примерно 25% набора динамических данных кучи Java в этих типах приложений используются объектами String. Кроме того, примерно половина из этих объектов String являются дубликатами, где дубликаты означают значение string1.equals (string2). Наличие дублированных объектов String в куче, по сути, просто трата памяти. ...
В Java 8, обновление 20, JEP 192 (мотивация приведена выше) реализуется для решения этой проблемы. Не вдаваясь в детали того, как работает дедупликация строк, важно, чтобы сами строки были неизменными. Вы не можете дедуплицировать StringBuilders, потому что они могут измениться, и вы не хотите, чтобы кто-то что-то изменял из-под вас. Неизменяемые строки (связанные с этим пулом строк) означают, что вы можете пройти через них, и если вы найдете две одинаковые строки, вы можете указать одну строковую ссылку на другую и позволить сборщику мусора поглотить вновь неиспользованную.
Другие языки
Цель C (которая предшествует Java) имеет NSString
и NSMutableString
.
C # и .NET сделали те же самые варианты дизайна строки по умолчанию, являющейся неизменной.
Строки Lua также неизменны.
Питон также.
Исторически, Lisp, Scheme, Smalltalk все интернируют строку и поэтому имеют ее неизменяемость. Более современные динамические языки часто используют строки некоторым образом, который требует, чтобы они были неизменяемыми (это может быть не строка , но она неизменна).
Заключение
Эти конструктивные соображения были сделаны снова и снова на множестве языков. По общему мнению, неизменяемые строки при всей их неловкости лучше альтернатив и приводят к лучшему коду (меньше ошибок) и в целом к более быстрым исполняемым файлам.