Зачем использовать GetHashCode() над Equals()?
HashSet<T>.Add
сначала сравнивает результаты GetHashCode
. Если они равны, он вызывает Equals
.
Теперь, я понимаю, что для реализации GetHashCode
нужно что-то делать с полями объекта. Простую примерную реализацию можно найти в Каков наилучший алгоритм для переопределенного System.Object.GetHashCode?.
В моем тестировании, сравнивающем как 1.000.000 пар объектов, заполненных случайными данными, производительность более или менее одинакова между ними. GetHashCode
реализуется как в связанном примере, Equals
просто вызывает Equals
для всех полей. Итак, почему нужно использовать GetHashCode
над Equals
?
Ответы
Ответ 1
Для некоторых типов тест Equals
может быть относительно дорогим. Обычно он должен сравнивать каждое поле класса. Другими словами, требуется линейное время в размере класса. Более высокие классы дороже сравнивать для равенства.
Теперь, что произойдет, если вам нужно сравнить один объект с 1000 другими? Вызов Equals
1000 раз может стать дорогим. Вам нужно сделать N * 2000 доступ к полям, если N - размер класса
GetHashCode
вместо этого генерирует "в основном уникальное" целое число, основанное на содержимом класса. Другими словами, поля классов доступны один раз. И как только вы это сделаете, вы можете сравнить это целое число с целыми числами 1000, которые составляют хэш-коды других объектов.
Даже в таком наивном случае использования нам нужны только образы полей N * 1000.
Но что, если мы храним хэш-код? Когда мы вставляем объект в хэш-набор, его хэш-код вычисляется один раз. Теперь, когда мы хотим сделать поиск в хэш-наборе, нам просто нужно вычислить один хэш-код (внешний объект), а затем вам просто нужно сравнить простые целые числа.
Таким образом, для доступа к классу N классов (для нового объекта, чей хэш-код нам нужно вычислить), плюс ряд целочисленных сравнений, которые варьируются в зависимости от алгоритма, но являются 1) относительно небольшими и 2) дешевыми.
Ответ 2
Потому что, если алгоритм хочет протестировать, если 1 объект уже находится в наборе из 1.000.000 объектов, он должен вызывать Equals
1.000.000 раз, но GetHashCode()
только один раз (и несколько вызовов на Equals
для устранения объектов, которые отличаются друг от друга, имея один и тот же хэш-код).
Ответ 3
GetHashCode()
получает интегральное значение, которое вы можете использовать для хэш-таблиц. Этот хэш-код является одной из причин, почему хеш-таблицы настолько эффективны. Однако может быть более одного объекта с одним и тем же хэш-кодом. Вот почему вызывается Equals()
. Если объекты не равны, они могут перейти в одно и то же ведро, если они равны, то он уже находится в хеш-таблице и не нужно добавлять.
Ответ 4
GetHashCode позволяет помещать вещи в ведра - несколько объектов могут иметь один и тот же хэш-код. Затем Equals используются для поиска совпадений внутри ведра. Это позволяет быстро находить вещи в больших коллекциях.
Ответ 5
Существенным аспектом GetHashCode
является то, что наблюдение, которое отличаются друг от друга хэш-кодами двух объектов, представляет собой не только наблюдение, что объекты разные, но и наблюдение чего-то гораздо более мощного: если хэш-коды всех элементов в одном наборе имеют свойство, не имеющее свойств всех объектов в другом, тогда наборы не имеют общих элементов.
Например, если вы помещаете в один набор все объекты, где GetHashCode
возвращает четное число, а в другой набор все объекты, где GetHashCode
возвращает нечетное число, затем предоставляется объект для поиска, вызова GetHashCode
позволит мгновенно исключить из рассмотрения все объекты в одном из множеств. Если вместо использования двух наборов один использовал двадцать, можно было бы устранить все из девятнадцати наборов. Если 256 наборов, можно устранить 255. Во многих случаях, если вы отрегулируете количество наборов, основанных на количестве элементов, которые есть, можно будет исключить все, кроме нескольких объектов, без необходимости смотреть на любой из них.
Глядя на хэш-коды двух объектов, чтобы увидеть, могут ли они быть равными, редко будет быстрее, чем просто проверять объекты непосредственно на равенство. С другой стороны, имея возможность знать, что один объект не равен 999,990, другие, не смотря на них, могут быть намного быстрее, чем смотреть на них, независимо от того, насколько быстро сравним равенство.