Существует один случай, когда с помощью hashcodes как укороченный на сравнениях равенства имеет смысл.
Рассмотрите случай, когда вы строите хэш-таблицу или хешсет. На самом деле, давайте просто рассмотрим хэшеты (hashtables расширяют это, также сохраняя значение, но это не актуально).
Существует множество различных подходов, которые можно предпринять, но во всех них у вас есть небольшое количество слотов, в которые могут быть помещены хешированные значения, и мы берем либо открытый, либо закрытый подход (который просто для удовольствия, некоторые люди используйте противоположный жаргон для других); если мы сталкиваемся в одном слоте для двух разных объектов, мы можем либо хранить их в одном слоте (но имея связанный список или такой, где объекты фактически хранятся), либо путем повторного зондирования, чтобы выбрать другой слот (существуют различные стратегии для этого).
Теперь, при любом подходе мы отходим от сложности O (1), которую мы хотим с помощью хеш-таблицы, и к сложности O (n). Риск этого обратно пропорционален количеству доступных слотов, поэтому после определенного размера мы изменяем размер хеш-таблицы (даже если все было идеально, мы должны были бы это сделать, если количество сохраненных элементов было больше, чем количество слоты).
Повторная установка элементов при изменении размера, очевидно, будет зависеть от хэш-кодов. Из-за этого, хотя редко бывает смысл запоминать GetHashCode()
в объекте (он просто не называется достаточно часто на большинстве объектов), он, безусловно, имеет смысл его memoise внутри самой хеш-таблицы (или, возможно, для memoise произведенной результат, например, если вы повторно хешировали хэшем Wang/Jenkins, чтобы уменьшить ущерб, вызванный плохими GetHashCode()
реализациями).
Теперь, когда мы приходим, чтобы вставить нашу логику будет что-то вроде:
- Получить хэш-код для объекта.
- Получить слот для объекта.
- Если слот пуст, поместите в него объект и верните его.
- Если слот содержит равный объект, мы делаем для хешета и имеем место, чтобы заменить значение хэш-таблицы. Сделайте это и вернитесь.
- Попробуйте выполнить следующий слот в соответствии со стратегией столкновения и вернитесь к пункту 3 (возможно, измените размер, если мы зацикливаем это слишком часто).
Итак, в этом случае мы должны получить хэш-код, прежде чем сравнивать его для равенства. У нас также есть хеш-код для существующих объектов, предварительно запрограммированных для разрешения изменения размера. Сочетание этих двух фактов означает, что это имеет смысл реализовать наше сравнение по пункту 4, как:
private bool IsMatch(KeyType newItem, KeyType storedItem, int newHash, int oldHash)
{
return ReferenceEquals(newItem, storedItem) // fast, false negatives, no false positives (only applicable to reference types)
||
(
newHash == oldHash // fast, false positives, no fast negatives
&&
_cmp.Equals(newItem, storedItem) // slow for some types, but always correct result.
);
}
Очевидно, что преимущество этого зависит от сложности _cmp.Equals
. Если бы наш ключевой тип был int
, тогда это было бы полным отходом. Если наш ключевой тип, в котором используется строка, и мы использовали независимые от Unicode сравнения сравнений с выравниванием по Юникоду (поэтому он не может даже содержать ярлык по длине), то экономия может стоить того.
Как правило, мемориальные хеш-коды не имеют смысла, потому что они недостаточно часто используются для выигрыша производительности, но хранение их в hashset или hashtable может иметь смысл.
Как разработчик, вы обязаны сделать это ради себя, чтобы в полной мере понять, что хэши используются и как они относятся к хэш-таблицам (как это реализовано в Словарере и HashSet, среди прочих). Статья в wikipedia для хэш-таблицы - это хорошее начало: http://en.wikipedia.org/wiki/Hash_table – spender 2010-11-22 19:00:53
@spender - это именно то, что этот вопрос объяснил мне более подробно, чем я изначально понял или мог вспомнить. – Armbrat 2010-11-22 19:03:25