Почему внутренняя реализация HashSet создает фиктивные объекты для вставки как значений в HashMap вместо вставки нулей?
HashSet реализуется с использованием HashMap, и когда мы добавляем что-либо, скажем, e1 в HashSet, внутри он добавляет (e1, новый Object()) в HashMap, если e1 не присутствовал в наборе. Мой вопрос заключается в том, почему они вставляют новый Object(), когда они могут вставлены как (e1, null), что является более оптимизированным подходом, так как новые объекты не создаются. Есть ли недостаток в вставке нулей здесь?
Ответы
Ответ 1
HashSet
не добавляет новый Object
каждый раз, когда новый ключ put
на карту. Он использует Object
, но каждый раз использует один и тот же Object
. Это значение называется PRESENT
в исходном коде HashSet
.
Метод add
вызывает put(key, PRESENT)
на внутренней HashMap
. Метод remove
вызывает remove(key)
на внутренней HashMap
, но он должен возвращать boolean
указывающее, присутствовал ли ключ. Если значение null
было сохранено как значение, тогда HashSet
необходимо сначала вызвать containsKey
, а затем remove
, чтобы определить, присутствует ли ключ - дополнительные служебные данные. Здесь есть только накладные расходы на память одного Object
, что является весьма минимальным.
Ответ 2
Я просто посмотрел на исходный код и увидел этот код
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
Они не будут работать, если вместо PRESENT
используется значение null
; в каждом случае потребуется дополнительный шаг.
Ответ 3
Например, если вы передаете объект HashSet конструктору ConcurrentSkipListSet, он не может содержать никаких нулевых значений.