Мне просто интересно, почему эти простые числа используются в hashCode()
методе класса ? Например, при использовании Eclipse для генерации моего hashCode()
метода всегда используется простое число 31
:
public int hashCode() {
final int prime = 31;
//...
}
Ссылки:
Вот хороший учебник по Hashcode и статья о том, как работает хеширование, которую я нашел (C #, но концепции переносимы): Руководство и правила Эрика Липперта для GetHashCode ()
Ответы:
Потому что вы хотите, чтобы число, на которое вы умножали, и количество блоков, в которые вы вставляете, имели ортогональные простые факторизации.
Предположим, есть 8 ведер для вставки. Если число, которое вы используете для умножения, кратно 8, то вставленный сегмент будет определяться только наименее значимой записью (единица, не умноженная вообще). Подобные записи будут сталкиваться. Не подходит для хэш-функции.
31 - достаточно большое простое число, которое вряд ли будет делиться им на количество сегментов (и на самом деле современные реализации Java HashMap поддерживают количество сегментов в степени 2).
источник
(x*8 + y) % 8 = (x*8) % 8 + y % 8 = 0 + y % 8 = y % 8
Простые числа выбираются так, чтобы наилучшим образом распределять данные по хэш-корзинам. Если распределение входов является случайным и равномерно распределенным, то выбор хеш-кода / модуля не имеет значения. Это оказывает влияние, только когда есть определенный шаблон для входов.
Это часто имеет место при работе с областями памяти. Например, все 32-разрядные целые числа выровнены по адресам, кратным 4. Посмотрите на таблицу ниже, чтобы визуализировать эффекты использования простого и не простого модуля:
Обратите внимание на почти идеальное распределение при использовании простого модуля против не простого модуля.
Однако, хотя приведенный выше пример в значительной степени надуманный, общий принцип заключается в том, что при работе с шаблоном входных данных использование модуля простых чисел даст наилучшее распределение.
источник
Во что бы то ни стало, Effective Java 2nd Edition отказывается от математической проблемы и просто говорит, что причина выбора 31:
Вот полная цитата из пункта 9: Всегда переопределять
hashCode
при переопределенииequals
:Проще говоря, можно сказать, что использование множителя с многочисленными делителями приведет к большему количеству коллизий хешей . Поскольку для эффективного хеширования мы хотим минимизировать количество коллизий, мы стараемся использовать множитель, который имеет меньше делителей. Простое число по определению имеет ровно два разных положительных делителя.
Смежные вопросы
источник
3, 5, 17, 257, 65537
или 2 ^ N - 1 ( простых чисел Мерсенна ):3, 7, 31, 127, 8191, 131071, 524287, 2147483647
. Однако31
(а не, скажем,127
) выбран.Я слышал, что 31 был выбран так, чтобы компилятор мог оптимизировать умножение до 5 битов влево и затем вычесть значение.
источник
mov reg1, reg2-shl reg1,5-sub reg1,reg2
может выполняться в 2 цикла. (mov это просто переименование и занимает 0 циклов).Вот цитата немного ближе к источнику.
Это сводится к:
источник
Сначала вы вычисляете значение хеша по модулю 2 ^ 32 (размер an
int
), поэтому вы хотите получить что-то относительно простое до 2 ^ 32 (относительно простое означает, что общих делителей нет). Для этого подойдет любое нечетное число.Затем для данной хеш-таблицы индекс обычно вычисляется из хеш-значения по модулю размера хеш-таблицы, поэтому вы хотите что-то, что является относительно простым по отношению к размеру хеш-таблицы. По этой причине часто размеры хеш-таблиц выбираются как простые числа. В случае Java реализация Sun гарантирует, что размер всегда является степенью двойки, поэтому и нечетного числа здесь тоже будет достаточно. Существует также дополнительная массование хеш-ключей для дальнейшего ограничения коллизий.
Плохой эффект, если хеш-таблица и множитель имеют общий фактор,
n
могут заключаться в том, что при определенных обстоятельствах будут использоваться только 1 / n записей в хеш-таблице.источник
Причина, по которой используются простые числа, состоит в том, чтобы минимизировать коллизии, когда данные демонстрируют некоторые конкретные закономерности.
Перво-наперво: если данные случайные, тогда нет необходимости в простом числе, вы можете выполнить операцию мода для любого числа, и у вас будет одинаковое количество столкновений для каждого возможного значения модуля.
Но когда данные не случайны, происходят странные вещи. Например, рассмотрим числовые данные, которые всегда кратны 10.
Если мы используем мод 4, мы находим:
10 мод 4 = 2
20 мод 4 = 0
30 мод 4 = 2
40 мод 4 = 0
50 мод 4 = 2
Таким образом, из 3 возможных значений модуля (0,1,2,3) только 0 и 2 будут иметь столкновения, что плохо.
Если мы используем простое число, такое как 7:
10 мод 7 = 3
20 мод 7 = 6
30 мод 7 = 2
40 мод 7 = 4
50 мод 7 = 1
и т.д
Мы также отмечаем, что 5 не является хорошим выбором, но 5 простое, потому что все наши ключи кратны 5. Это означает, что мы должны выбрать простое число, которое не делит наши ключи, выбор большого простого числа обычно достаточно.
Таким образом, ошибочная сторона повторения приводит к тому, что простые числа используются для нейтрализации влияния шаблонов в ключах при распределении коллизий хэш-функции.
источник
31 также специфичен для Java HashMap, который использует int как тип хеш-данных. Таким образом, максимальная емкость 2 ^ 32. Нет смысла использовать большие простые числа Ферма или Мерсенна.
источник
Как правило, это помогает добиться более равномерного распределения ваших данных по хэш-корзинам, особенно для ключей с низкой энтропией.
источник