Sure:
public class Test {
private final int m, n;
public Test(int m, int n) {
this.m = m;
this.n = n;
}
public int hashCode() { return n * m; }
public boolean equals(Object ob) {
if (ob.getClass() != Test.class) return false;
Test other = (Test)ob;
return m == other.m;
}
}
с:
Set<Test> set = new HashSet<Test>();
set.put(new Test(3,4));
boolean b = set.contains(new Test(3, 10)); // false
Технически это должно быть правдой, потому что м == 3 в обоих случаях.
В общем, HashMap работает следующим образом: он имеет переменное число, которое обычно называют «ведрами». Количество ведер может меняться со временем (при добавлении и удалении записей), но оно всегда равно 2.
Предположим, что данный HashMap
имеет 16 ковшей. Когда вы вызываете put() для добавления записи, вычисляется hashCode() ключа, а затем берется маска в зависимости от размера ведер. Если вы (побитовое) и хэш-код() с 15 (0x0F) вы получите последние 4 бита, сравнявшись число от 0 до 15 включительно:
int factor = 4;
int buckets = 1 << (factor-1) - 1; // 16
int mask = buckets - 1; // 15
int code = key.hashCode();
int dest = code & mask; // a number from 0 to 15 inclusive
Теперь, если уже есть запись в этом ведре вы имеют так называемое столкновение . Существует несколько способов борьбы с этим, но тот, который используется HashMap (и, вероятно, является наиболее распространенным), составляет bucketing. Все записи с тем же самым замаскированным хэш-кодом помещаются в какой-то список.
Так, чтобы найти, если данный ключ в карте уже:
- Вычислить замаскированный хэш-код;
- Найти подходящее ведро;
- Если он пуст, ключ не найден;
- Если значение не пустое, пропустите все записи в ведре, проверяя равен().
Просматривая ведро - это линейная операция (O (n)), но она находится на небольшом подмножестве. Определение ковша hashcode по существу постоянное (O (1)). Если ведра достаточно малы, то доступ к HashMap обычно описывается как «около O (1)».
Вы можете сделать пару замечаний об этом.
Во-первых, если у вас есть куча объектов, которые все возвращают 42 в качестве своего хеш-кода, то HashMap
все равно будет работать, но он будет работать как дорогой список. Доступ будет O (n) (поскольку все будет в одном ковше независимо от количества ведер). На самом деле меня спрашивали в интервью.
Во-вторых, возвращаясь к исходной точке, если два объекта равны (означая. equals(b) == b.equals(a) == true
), но имеют разные хэш-коды, то HashMap
будет искать в (возможно) неправильное ведро, что приводит к непредсказуемым и непредсказуемое поведение.
Это не ответ, а просто обратите внимание, что * цель цели * hashCode() состоит в том, чтобы предоставить число, которое должен был бы предоставить любой равный объект. Если бы не это свойство, у него не было бы причин существовать. –