Что происходит с поиском в Hashmap или Hashset при изменении объектов Hashcode
В Hashmap хэш-код предоставленного ключа используется для размещения значения в хэш-таблице. В Hashset используется hashcode объектов, чтобы поместить значение в базовую хэш-таблицу. то есть преимущество хэшмапа в том, что у вас есть гибкость в выборе того, что вы хотите в качестве ключа, чтобы вы могли делать такие приятные вещи.
Map<String,Player> players = new HashMap<String,Player>();
Это может отображать строку, такую как имя игрока, самому игроку.
Мой вопрос заключается в том, что происходит с поиском, когда изменяется ключ Hashcode.
Я ожидаю, что это не такая серьезная проблема для Hashmap, как я бы не ожидал и не хотел, чтобы ключ менялся. В предыдущем примере, если имена игроков изменяются, он больше не является игроком. Однако я могу посмотреть на игрока, используя изменение ключа. Другие поля, которые не являются именем, и будущие поисковые запросы будут работать.
Однако в Hashset, поскольку весь объект hashcode используется для размещения элемента, если кто-то слегка изменяет объект, будущие поиски этого объекта больше не будут разрешаться к одной и той же позиции в Hashtable, поскольку он полагается на все объекты Hashcode. Означает ли это, что, как только данные находятся в Hashset, его не следует изменять. Или нужно его перефразировать? или это делается автоматически и т.д.? Что происходит?
Ответы
Ответ 1
В вашем примере строка является неизменной, поэтому ее хэш-код не может измениться. Но гипотетически, если хэш-код объекта действительно изменился, когда был ключом в хеш-таблице, то он, вероятно, исчез бы до поисков хеш-таблиц. Я подробно рассмотрел этот ответ на соответствующий вопрос: fooobar.com/questions/538442/.... (Первоначальный вопрос касается HashSet
, но HashSet
действительно является HashMap
под обложками, поэтому ответ также охватывает этот случай.)
Можно с уверенностью сказать, что если ключи HashMap или TreeMap мутируются таким образом, что они влияют на их соответствующие контракты hashcode()
/equals(Object)
или compare(...)
или compareTo(...)
, тогда структура данных будет "сломать".
Означает ли это, что, как только данные находятся в Hashset, это не должно быть изменено.
Да.
Или нужно ли его перефразировать? или это делается автоматически и т.д.
Он не будет автоматически перезагружен. HashMap
не заметит, что хэш-код ключа изменился. В самом деле, вы даже не сможете пересчитать хэш-код, когда размер HashMap
изменится. Структура данных запоминает исходное значение hashcode, чтобы избежать необходимости пересчитывать все хэш-коды при изменении размера хэш-таблицы.
Если вы знаете, что хэш-код ключа изменится, вам нужно удалить запись из таблицы, прежде чем вы будете мутировать ключ, и добавьте его обратно. (Если вы попытаетесь выполнить remove
/put
после мутации ключа, есть вероятность, что remove
не сможет найти запись.)
Что происходит?
Что происходит, так это то, что вы нарушили контракт, четко изложенный в javadocs HashMap
. Не делай этого!
Ответ 2
В вашем примере клавиши String являются неизменяемыми. Таким образом, хэш-код ключей не изменится. Что происходит, когда хэш-код ключей изменяется undefined и приводит к "странному" поведению. См. Пример ниже, который печатает 1, false и 2. Объект остается в наборе, но набор выглядит как он сломан (содержит возвращает false).
Извлечь из Установить javadoc:
Примечание. Следует проявлять большую осторожность, если изменяемые объекты используются в качестве заданных элементов. Поведение набора не указывается, если значение объекта изменяется таким образом, который влияет на равные сравнения, когда объект является элементом в наборе. Особый случай этого запрета состоит в том, что недопустимо, чтобы набор содержал себя как элемент.
public static void main(String args[]) {
Set<MyObject> set = new HashSet<>();
MyObject o1 = new MyObject(1);
set.add(o1);
o1.i = 2;
System.out.println(set.size()); //1
System.out.println(set.contains(o1)); //false
for (MyObject o : set) {
System.out.println(o.i); //2
}
}
private static class MyObject {
private int i;
public MyObject(int i) {
this.i = i;
}
@Override
public int hashCode() {
return i;
}
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final MyObject other = (MyObject) obj;
if (this.i != other.i) return false;
return true;
}
}
Ответ 3
HashSet
создается HashMap
.
Из javadocs.
Этот класс реализует интерфейс Set, поддерживаемый хэш-таблицей (на самом деле экземпляр HashMap).
Итак, если вы измените хэш-код, я сомневаюсь, что вы можете получить доступ к объекту.
Внутренние параметры реализации
Реализация HashSet
HashSet
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Ключ - это элемент, а значение - только фиктивный объект, называемый PRESENT
а реализация contains
-
public boolean contains(Object o) {
return map.containsKey(o);
}
Ответ 4
С хэшами Java исходная ссылка просто не найдена. Он искал в ведре соответствующий текущий хэш-код и не нашел.
Чтобы восстановить это после факта, необходимо выполнить итерацию набора ключей Hash, и любой ключ, который не найден методом contains
, должен быть удален через итератор. Предпочтительно удалить ключ с карты, а затем сохранить значение с помощью нового ключа.