Почему хеш-код Java () в строке использует 31 в качестве множителя?

в Java хэш-код на String объект вычисляется как

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

используя int арифметика, где s[i] - это ith символ строки n - длина строки, и ^ указывает на возведение в степень.

почему 31 используется в качестве множителя?

Я понимаю, что множитель должен быть относительно большим простым числом. Так почему не 29 или 37, или даже 97?

10 ответов


по словам Джошуа Блоха Эффективная Java (книга, которую нельзя рекомендовать достаточно, и которую я купил благодаря постоянным упоминаниям о stackoverflow):

значение 31 было выбрано, потому что это нечетное простое число. Если бы он был четным и умножение переполнено, информация была бы потеряна, так как умножение на 2 эквивалентно сдвигу. Преимущество использования простого числа менее ясно, но оно является традиционным. Хорошим свойством 31 является то, что умножение может быть заменено сдвигом и вычитанием для лучшей производительности:31 * i == (i << 5) - i. Современные виртуальные машины делают такую оптимизацию автоматически.

(из главы 3, пункт 9: всегда переопределять хэш-код при переопределении equals, стр. 48)


As Гудрич и Tamassia укажите, если вы возьмете более 50 000 английских слов (сформированных как объединение списков слов, представленных в двух вариантах Unix), использование констант 31, 33, 37, 39 и 41 приведет к менее чем 7 столкновениям в каждом случае. Зная это, неудивительно, что многие реализации Java выбирают одну из этих констант.

по совпадению, я был в середине чтения раздела "полиномиальные хэш-коды", когда я увидел это вопрос.

EDIT: вот ссылка на книгу ~10mb PDF, о которой я говорю выше. См. раздел 10.2 хэш-таблицы (стр. 413)структуры данных и алгоритмы в Java


на (в основном) старых процессорах, умножение на 31 может быть относительно дешевым. На руке, например, только одна инструкция:--2-->

RSB       r1, r0, r0, ASL #5    ; r1 := - r0 + (r0<<5)

большинству других процессоров потребуется отдельная команда shift и subtract. Однако, если ваш множитель медленный, это все еще победа. Современные процессоры, как правило, имеют быстрые множители, поэтому это не имеет большого значения, пока 32 идет на правильной стороне.

это не отличный алгоритм хэша, но он достаточно хорош и лучше, чем код 1.0 (и намного лучше, чем 1.0 spec!).


при умножении биты смещаются влево. Это использует больше доступного пространства хэш-кодов, уменьшая коллизии.

не используя силу двух, заполняются также младшие, самые правые биты, которые должны быть смешаны со следующим фрагментом данных, входящим в хэш.

выражение n * 31 эквивалентно (n << 5) - n.


вы можете прочитать оригинальное рассуждение Блоха в разделе "Комментарии" в http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622. Он исследовал производительность различных хэш-функций в отношении результирующего "среднего размера цепочки" в хэш-таблице. P(31) была одной из распространенных функций в то время, которую он нашел в книге K&R (но даже Керниган и Ричи не могли вспомнить, откуда она взялась). В конце концов, он в основном должен был выбрать один, и поэтому он взял P(31) с тех пор, как он, казалось, работал достаточно хорошо. Хотя P(33) не было действительно хуже, и умножение на 33 одинаково быстро вычисляется (просто сдвиг на 5 и сложение), он выбрал 31, так как 33 не является простым:

остальных в-четвертых, я бы, вероятно, выбрал P (31), так как он самый дешевый для расчета на RISC машина (потому что 31-это разница двух степеней двух). P (33) is аналогично дешево рассчитать, но его производительность незначительно хуже, и 33 is смесь, которая заставляет меня немного нервничать.

таким образом, рассуждение не было столь рациональным, как многие из ответов здесь, похоже, подразумевают. Но мы все хорошо придумываем рациональные причины после решений кишечника (и даже блох может быть склонен к этому).


На самом деле, 37 будет работать довольно хорошо! z: = 37 * x можно вычислить как y := x + 8 * x; z := x + 4 * y. Оба шага соответствуют одной инструкции Lea x86, поэтому это очень быстро.

на самом деле, умножение с еще большим простым 73 можно сделать с той же скоростью, установив y := x + 8 * x; z := x + 8 * y.

через 73 или 37 (вместо 31) может быть лучше, потому что это приводит к более плотный код: две инструкции LEA занимают только 6 байтов против 7 байтов для перемещение + сдвиг+вычитание для умножения на 31. Одним из возможных предостережений является то, что 3-аргументные инструкции LEA, используемые здесь, стали медленнее в архитектуре Sandy bridge от Intel с увеличенной задержкой в 3 цикла.

кроме того, 73 любимый номер Шелдона Купера.


Нил Коффи объясняется почему 31 используется под сглаживание смещения.

в основном использование 31 дает вам более равномерное распределение вероятности set-bit для хэш-функции.


Я не уверен, но я бы предположил, что они проверили некоторую выборку простых чисел и обнаружили, что 31 дал лучшее распределение по некоторой выборке возможных строк.


блох не совсем вдается в это, но я всегда слышал/верил, что это базовая алгебра. Хэши сводятся к операциям умножения и модуля, что означает, что вы никогда не хотите использовать числа с общими факторами, если можете помочь. Другими словами, относительно простые числа обеспечивают равномерное распределение ответов.

числа, которые составляют использование хэша, обычно:

  • модуль типа данных, в который вы его вводите (2^32 или 2^64)
  • модуль подсчета ведра в вашей хэш-таблице (варьируется. В java раньше было prime, теперь 2^n)
  • умножьте или сдвиньте на магическое число в вашей функции смешивания
  • входное значение

вы действительно можете контролировать только пару из этих значений,поэтому немного дополнительного ухода.


С JDK-4045622, где Джошуа блох описывает причины, почему этот конкретный (новый) String.hashCode() была выбрана реализация

в таблице ниже приведены данные по производительности различных хэш функции, описанные выше, для трех наборов данных:

1) Все слова и фразы с записями в Merriam-Webster's 2-й Int'L Unabridged Dictionary (311,141 строк, длина avg 10 символов).

2) все строки in / bin/, / usr / bin/, / usr / lib/, / usr/ucb/ и / usr / openwin/ bin / * (66,304 строки, длина avg 21 символ).

3) список URL-адресов, собранных веб-искателем, который выполнялся для нескольких часы прошлой ночи (28,372 строки, длина avg 49 символов).

метрика производительности, показанная в таблице, является " средним размером цепи" над всеми элементами хэш-таблицы (т. е. ожидаемым значением ряд ключевых сравнивает чтобы найти элемент).

                          Webster's   Code Strings    URLs
                          ---------   ------------    ----
Current Java Fn.          1.2509      1.2738          13.2560
P(37)    [Java]           1.2508      1.2481          1.2454
P(65599) [Aho et al]      1.2490      1.2510          1.2450
P(31)    [K+R]            1.2500      1.2488          1.2425
P(33)    [Torek]          1.2500      1.2500          1.2453
Vo's Fn                   1.2487      1.2471          1.2462
WAIS Fn                   1.2497      1.2519          1.2452
Weinberger's Fn(MatPak)   6.5169      7.2142          30.6864
Weinberger's Fn(24)       1.3222      1.2791          1.9732
Weinberger's Fn(28)       1.2530      1.2506          1.2439

глядя на эту таблицу, ясно, что все функции, кроме текущая функция Java и две сломанные версии Weinberger функция предлагает превосходное, почти неразличимое представление. Я сильно предполагаю, что эта производительность по существу "теоретический идеал", который вы получите, если используете истинный случайный генератор чисел вместо хэш-функции.

Я бы исключил WAIS функция как своя спецификация содержит страницы случайных чисел, и свое представление не более лучшее чем любое из гораздо более простые функции. Любая из оставшихся шести функций выглядит так: отличный выбор, но мы должны выбрать один. Полагаю, я бы исключил Вариант Vo и функция Вайнбергера из-за их добавленного сложность, хотя и незначительная. Из оставшихся четырех я бы, наверное, выбрал P (31), так как это самый дешевый для расчета на машине RISC (потому что 31 разница двух держав из двух). P (33) аналогично дешево рассчитать, но это производительность незначительно хуже, и 33 смесь, которая заставляет меня немного нервничать.

Джош