NullPointerException
s - исключения, возникающие при попытке использовать ссылку, которая указывает на отсутствие местоположения в памяти (null), как если бы она ссылалась на объект. Вызов метода по нулевой ссылке или попытка получить доступ к полю нулевой ссылки вызовет функцию NullPointerException
. Они наиболее распространены, но другие способы перечислены на странице NullPointerException
javadoc.
Вероятно, самый быстрый пример кода, который я мог бы придумать для иллюстрации NullPointerException
, be:
public class Example {
public static void main(String[] args) {
Object obj = null;
obj.hashCode();
}
}
В первой строке внутри main
я явно устанавливаю ссылку Object
obj
равной null
. Это означает, что у меня есть ссылка, но она не указывает на какой-либо объект. После этого я пытаюсь обработать ссылку так, как если бы она указывала на объект, вызывая метод на нем. Это приводит к NullPointerException
, потому что нет кода для выполнения в местоположении, на которое указывает ссылка.
(Это техничность, но я думаю, что она упоминает: ссылка, которая указывает на null, равна 't то же, что и указатель C, указывающий на недопустимую ячейку памяти. Нулевой указатель буквально не указывает на в любом месте , который отличается от указаний на местоположение, которое оказывается недопустимым.)
На самом деле это не просто HashSet
. Все реализации интерфейса Set
в Java 6 основаны на базовом Map
. Это не требование; это просто способ реализации. Вы можете убедиться сами, проверив документацию по различным реализациям Set
.
Ваши основные вопросы:
Но почему он все еще используется? Есть ли причина использовать его, кроме упрощения ведения кодов?
Я предполагаю, что обслуживание кода является большим мотивирующим фактором. Таким образом, предотвращается дублирование и раздувание.
Set
иMap
- это аналогичные интерфейсы, поскольку дублирующиеся элементы не допускаются. (Я думаю, что единственныйSet
, а не , поддерживаемыйMap
, -CopyOnWriteArraySet
, что является необычной коллекцией, потому что оно неизменное.)В частности:
Из документации
Set
:Коллекция, которая не содержит повторяющихся элементов. Более формально множества не содержат пары элементов e1 и e2 таких, что e1.equals (e2) и не более одного нулевого элемента. Как показано в его названии, этот интерфейс моделирует абстрактную абстракцию математического набора.
Интерфейс Set устанавливает дополнительные условия, помимо тех, которые унаследованы из интерфейса Collection, в контрактах всех конструкторов и контрактах add, equals и hashCode. Объявления для других унаследованных методов также включены здесь для удобства. (Спецификации, прилагаемые к этим объявлениям, были адаптированы к интерфейсу Set, но они не содержат каких-либо дополнительных условий.)
. Дополнительная оговорка о конструкторах не удивительно, что все конструкторы должны создать набор, содержащий
И из
Map
:Объект, который сопоставляет ключи со значениями , Карта не может содержать дубликаты ключей; каждый ключ может отображать не более одного значения.
Если вы можете реализовать свой
Set
s с использованием существующего кода, любое преимущество (например, скорость), которое вы можете реализовать из существующего кода, начисляется на вашSet
.Если вы решите реализовать
Set
без подставкиMap
, вам придется дублировать код, предназначенный для предотвращения дублирования элементов. Ах, восхитительная ирония.Тем не менее, ничего не мешает вам реализовать ваш
Set
по-другому.
После поиска таких страниц, как это задается вопросом, почему мягко неэффективная стандартная реализация найдена, com.carrotsearch.hppc.IntOpenHashSet
Я предполагаю, что HashSet изначально был реализован с точки зрения HashMap, чтобы сделать это быстро и легко. В терминах строк кода HashSet является частью HashMap.
Я бы предположил, что причина, по которой он еще не оптимизирован, - это страх перед изменением.
Однако, отходы намного хуже, чем вы думаете. На 32-битном и 64-битном уровне HashSet в 4 раза больше необходимого, а HashMap - 2x больше необходимого. HashMap может быть реализован с массивом с ключами и значениями в нем (плюс цепочки для коллизий). Это означает два указателя на запись или 16 байтов на 64-битной виртуальной машине. Фактически, HashMap содержит объект Entry для каждой записи, который добавляет 8 байтов для указателя на запись и 8 байтов для заголовка объекта Entry. HashSet также использует 32 байта на элемент, но отходы составляют 4 раза вместо 2x, поскольку для каждого элемента требуется только 8 байтов.
key
, value
и next
для обработки столкновений имеет в пять раз больше места по сравнению с одной ссылкой в плоском массиве (при сравнении с возможной реализацией Set
). Но в HashMap
все еще есть массив, массив ссылок на экземпляры Entry
. Таким образом, в конце HashMap
на основе HashSet
занимает примерно в шесть раз больше пространства плоского массива на основе HashSet
. На 64-битной JVM HotSpot с включенными CompressedOOP и CompressedKlassPointers это даже в 6.5 раз ...
– Holger
7 July 2018 в 14:50
HashMap
значительно изменилась за последнее десятилетие. И я не понимаю, почему вы так агрессивно настаиваете на том, чтобы отказаться от возможности улучшить улучшение в определенных сценариях. Это «4x.» Условно-досрочное освобождение, данное каким-то священным диктатором, который превосходит все технические обсуждения или что?
– Holger
9 July 2018 в 11:18
Я посмотрел на ваш вопрос, и мне потребовалось некоторое время, чтобы подумать о том, что вы сказали. Итак, вот мое мнение относительно реализации HashSet
.
Необходимо, чтобы фиктивный экземпляр знал, есть ли значение в наборе или нет.
Взгляните в методе add
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Теперь Abd посмотрим на возвращаемое значение put
@ возвращает предыдущее значение, связанное с ключом, или null, если не было отображение для ключа. (Нулевой возврат также может указывать, что ранее связанная карта с ключом.)
blockquote>Таким образом, объект
PRESENT
используется только для представления того, что набор содержит значение e. Я думаю, вы спросили, почему бы не использоватьnull
вместоPRESENT
. Но вы не сможете отличить, была ли запись ранее на карте, потому чтоmap.put(key,value)
всегда возвращалnull
, и у вас не было бы способа узнать, существовал ли ключ.
Сказав, что вы могли бы утверждать, что они могли использовать реализацию, подобную этой
public boolean add(E e) { if( map.containsKey(e) ) { return false; } map.put(e, null); return true; }
, я полагаю, что они тратят 4 байта, чтобы избежать вычисления хэш-кода, поскольку это может быть дорого, ключа два раза ( если ключ будет добавлен).
Если вы задали вопрос, почему они использовали
HashMap
, который будет тратить 8 байтов (из-заMap.Entry
) вместо некоторых других данных структура с использованием аналогичного входа только 4, то да, я бы сказал, что они сделали это по причинам, о которых вы упомянули.
Ваш вопрос: я думаю, что для размера самой записи тратится 4 байта (на 32-битных машинах).
Создана только одна переменная объекта для всей структуры данных хэшета, и это будет
private static final Object PRESENT = new Object();
Все ключи имеют одно значение, то есть объект PRESENT.
value
для всех записей в HashSet
.
– Stephen C
14 June 2018 в 13:08
Да, вы правы, небольшое количество потерь там определенно. Маленький, потому что для каждой записи он использует тот же объект PRESENT
(который объявлен окончательным). Следовательно, единственное израсходование - это значение для каждой записи в HashMap.
В основном, я думаю, они использовали этот подход для удобства и повторного использования. (Разработчики JCF подумали бы, что мы все же протестировали HashMap, почему бы не использовать его повторно.)
Но если у вас огромные коллекции, а вы - уродка памяти, то вы можете отказаться от лучших альтернатив например Trove или Google Collections .
Set
в Java 6 основаны на базовомCollection
. & quot; (Я предполагаю, что вы имеете в видуMap
вместоCollection
.) Существует по крайней мере один пример счетчика (кроме подмножеств и т.п.).EnumSet
не основан наMap
. – Tom Hawtin - tackline 6 January 2013 в 22:15