Согласно документации Java, хэш-код для объекта String вычисляется как:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]using
intarithmetic, wheres[i]is the ith character of the string,nis the length of the string, and^indicates exponentiation.
Почему 31 используется как множитель?
Я понимаю, что множитель должен быть относительно большим простым числом. Так почему не 29, 37 или даже 97?
Если бы было 29, 37 или даже 97, вы бы спросили: «Почему не 31?»
@EJP важно знать причину выбора «нет». если только число не является результатом трюка с черной магией.
Об этом есть сообщение в блоге @ peter-lawrey здесь: vanilla-java.github.io/2018/08/12/… и здесь: vanilla-java.github.io/2018/08/15/…
@DushyantSabharwal Я хочу сказать, что у него может быть был 29, или 37, или 97, или 41, или многие другие значения, без особой практической разницы. В 1976 году мы использовали 37.




Я не уверен, но я предполагаю, что они протестировали некоторый образец простых чисел и обнаружили, что 31 дает наилучшее распределение по некоторому образцу возможных строк.
На (в основном) старых процессорах умножение на 31 может быть относительно дешевым. В ARM, например, есть только одна инструкция:
RSB r1, r0, r0, ASL #5 ; r1 := - r0 + (r0<<5)
Для большинства других процессоров потребуется отдельная инструкция сдвига и вычитания. Однако, если ваш множитель медленный, это все равно выигрыш. Современные процессоры, как правило, имеют быстрые множители, поэтому это не имеет большого значения, если 32 идет на правильную сторону.
Это не лучший алгоритм хеширования, но он достаточно хорош и лучше, чем код 1.0 (и намного лучше, чем спецификация 1.0!).
Как ни странно, умножение на 31 на моем настольном компьютере на самом деле немного медленнее, чем умножение, скажем, на 92821. Я предполагаю, что компилятор пытается «оптимизировать» его для сдвига и сложения. :-)
Я не думаю, что когда-либо использовал ARM, который не был бы одинаково быстрым со всеми значениями в диапазоне +/- 255. Использование степени 2 минус единица приводит к неудачному результату: изменение соответствия двух значений изменяет хэш-код на степень двойки. Значение -31 было бы лучше, и я думаю, что что-то вроде -83 (64 + 16 + 2 + 1) могло бы быть еще лучше (смешивание битов несколько лучше).
@supercat Не убедил минус. Кажется, ты вернешься к нулям. / String.hashCode предшествует StrongARM, который, IIRC, представил 8-битный умножитель и, возможно, увеличил его до двух циклов для комбинированных арифметических / логических операций со сдвигом.
@ TomHawtin-tackline: используя 31, хэш четырех значений будет 29791 * a + 961 * b + 31 * c + d; используя -31, это будет -29791 * a + 961 * b - 31 * c + d. Я не думаю, что разница будет значительной, если четыре элемента независимы, но если пары соседних элементов совпадают, полученный хэш-код будет вкладом всех непарных элементов плюс несколько кратных 32 (из парных). Для строк это может не иметь большого значения, но если вы пишете универсальный метод для хеширования агрегатов, ситуация, когда совпадают смежные элементы, будет непропорционально распространена.
Ситуация не так плоха, как та, которая возникает при использовании xor элементов, которые должны быть неупорядоченными (в отличие от использования арифметической суммы). Если у кого-то есть UnorderedPair, где A и B «равны», если (a.first.equals(b.first) && a.second.equals(b.second)) || ((a.first.equals(b.second) && a.second.equals(b.first)), использование хэша first.hashCode()+second.hashCode() приведет к потере одного бита информации, если совпадают first и second; использование first.hashCode() ^ second.hashCode() (что я видел как работающее) потеряло бы все 32 бита информации.
@supercat Да, мне кажется + дает небольшое "размытие" соседних битов, которое может быть перемешано в дальнейшем. ^ не имеет этого, поэтому кажется более плохим выбором. Я считаю, что ранние протоколы использовали xor в проверочных кодах, поскольку ошибочно полагали, что реализация аппаратного обеспечения будет проще из-за стоящего фактора.
@ TomHawtin-tackline: Использование xor в аппаратных реализациях дает ряд преимуществ. Кроме того, поведение xor ортогонально поведению сложения и умножения, его использование в сочетании с другими методами может улучшить «смешивание». В любом случае важно остерегаться случаев, когда непропорциональное появление определенных шаблонов ввода (например, пар совпадающих элементов) приведет к тому, что некоторые хэш-коды будут появляться гораздо чаще, чем должны.
@supercat забавный факт, хеш-код Map.Entry был исправлен спецификацией как key.hashCode() ^ value.hashCode(), несмотря на то, что это даже не неупорядоченная пара, поскольку key и value имеют совершенно разное значение. Да, это означает, что Map.of(42, 42).hashCode() или Map.of("foo", "foo", "bar", "bar").hashCode() и т. д. Предсказуемо равны нулю. Так что не используйте карты в качестве ключей для других карт ...
@Holger: Что грустно в этом, так это то, что использование + вместо ^ было бы столь же быстрым, но потеряло бы только один бит хэш-кода (а иногда и не то, поскольку хэш-коды некоторых типов никогда не бывают отрицательными).
@supercat и потери производительности из-за хеш-коллизий в любом случае превосходят любую экономию нескольких циклов ЦП при вычислении хэша ...
@Holger: Использование сложного хеша для предотвращения потери хеш-бита из x+y, вероятно, не окупится, но хеш, возвращающий ноль для всех ключей, просто трагичен.
@supercat, вы сделали правильный выбор. Даже на гипотетическом процессоре, который мог бы сэкономить цикл при использовании ^ вместо +, значительно большее количество коллизий съело бы это преимущество. Это только тогда, когда ключ и значение имеют один и тот же хэш-код, но это не слишком надумано, чтобы иметь такие сопоставления.
Согласно Эффективная Java Джошуа Блоха (книга, которую нельзя рекомендовать достаточно, и которую я купил благодаря постоянным упоминаниям о stackoverflow):
The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, as multiplication by 2 is equivalent to shifting. The advantage of using a prime is less clear, but it is traditional. A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance:
31 * i == (i << 5) - i. Modern VMs do this sort of optimization automatically.
(из главы 3, пункт 9: Всегда переопределять хэш-код при переопределении равенства, стр. 48)
Ну, все простые числа нечетные, кроме 2. Просто скажи.
Я не думаю, что Блох говорит, что он был выбран потому, что это было нечетное простое число, а потому, что оно было нечетным, И потому, что оно было простым (И потому, что его можно легко оптимизировать для сдвига / вычитания).
31 было выбрано, потому что это нечетное простое число ??? В этом нет никакого смысла - я говорю, что 31 был выбран, потому что он давал лучшее распределение - проверьте computinglife.wordpress.com/2008/11/20/…
Я считаю, что выбор 31 весьма неудачный. Конечно, это может сэкономить несколько циклов ЦП на старых машинах, но у вас уже есть хеш-коллизии в коротких строках ascii, таких как «@ и #!» Или Ca и DB. Этого не произойдет, если вы выберете, например, 1327144003 или минимум 524287, который также допускает битовый сдвиг: 524287 * i == i << 19 - i.
Но зачем умножать, он просто сдвигает младшие биты от элемента влево, после того, как достаточно элементов, все ваши биты сдвигаются влево и переполняются. возможно, если бы они были недостаточными, тогда все могло бы быть лучше.
@hstoerr: Я бы хотел увидеть вашу математику по этому поводу. Даже если вы правы (что вы, скорее всего, относитесь к двухзначному примеру), я думаю, что если вы посмотрите, как хеши используются в Java, на самом деле это не очень повредит, если будут коллизии в очень коротких строках. . Они не так часто используются для ключей.
@Jason Смотрите мой ответ stackoverflow.com/questions/1835976/…. Я хочу сказать, что вы получите гораздо меньше столкновений, если используете большее простое число, и в наши дни ничего не потеряете. Проблема усугубляется, если вы используете неанглийские языки с обычными символами, отличными от ascii. А 31 послужил плохим примером для многих программистов при написании собственных хэш-функций.
Действительно ли оптимизация сейчас помогает с учетом арифметических устройств в современных процессорах?
@hstoerr Полностью согласен с вашим. Использование 31 было ужасной идеей. Не должно быть конфликтов для строк длиной два и не должно быть конфликтов для строк ASCII длины четыре, но их много. Повторное использование 31 повсюду еще хуже, см., Например, эта моя крошечная тирада.
@RichardCorfield Я не уверен, имеет ли это смысл, но компилятор все еще использует его (см. Комментарии к этот ответ).
Каждый изучающий математику должен знать, почему он простой - он образует группу тогда и только тогда, когда он совпадает с размером группы.
Я не уверен, почему это было принято в качестве ответа. Я имею в виду, что это просто скопируйте пасту из известной книги !! Выпускник без математики и информатики вроде меня искал ответ с точки зрения непрофессионала. Прошу прощения, но ответ для меня слишком интеллектуален. Мне нужно больше искать в Google.
Как происходит «умножение на четное число с переполнением»? Переполнение может происходить даже с нечетными числами, правильно?
@FrankQ. Проблема не в переполнении: это, как вы говорите, неизбежно. Проблема в том, что умножение на четное число гарантирует, что меньшее количество битов будет содержать «изменяющуюся» информацию - младший бит становится нулевым. Всегда ноль. Вы потеряли немного «изменчивости». Результатом является более плохое распределение возможных значений хеш-функции.
Goodrich и Tamassia вычислили из более чем 50 000 английских слов (сформированных как объединение списков слов, представленных в двух вариантах Unix), что использование констант 31, 33, 37, 39 и 41 вызовет менее 7 коллизий в каждом случае. Это может быть причиной того, что многие реализации Java выбирают такие константы.
См. Раздел 9.2 Хеш-таблицы (стр. 522) в Структуры данных и алгоритмы в Java.
Обратите внимание, однако, что вы можете получить НАМНОГО больше коллизий, если используете любую международную кодировку с общими символами за пределами диапазона ASCII. По крайней мере, проверял на 31 и немецком. Так что думаю выбор 31 сломан.
При умножении биты сдвигаются влево. Это использует больше доступного пространства для хэш-кодов, уменьшая коллизии.
Если не использовать степень двойки, то младшие правые биты также заполняются для смешивания со следующей частью данных, попадающих в хэш.
Выражение n * 31 эквивалентно (n << 5) - n.
Блох не совсем вникает в это, но я всегда слышал / верил в обоснование того, что это базовая алгебра. Хеши сводятся к операциям умножения и модуля, а это означает, что вы никогда не захотите использовать числа с общими множителями, если можете. Другими словами, относительно простые числа обеспечивают равномерное распределение ответов.
Числа, которые составляют с помощью хеша, обычно:
На самом деле вы можете контролировать только несколько из этих значений, так что требуется небольшая дополнительная осторожность.
На самом деле, 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 стали медленнее в архитектуре Intel Sandy bridge с увеличенной задержкой на 3 цикла.
Более того, 73 - любимое число Шелдона Купера.
@Mainguy На самом деле это синтаксис АЛГОЛА, который довольно часто используется в псевдокоде.
но в сборке ARM умножение на 31 может быть выполнено в одной инструкции
@Mainguy Что означает: = в псевдокоде?
В TPOP (1999) можно прочитать о ранней версии Java (стр.57): «... Проблема была решена заменой хэша на один эквивалентный тому, который мы показали (с множителем 37) ...»
Нил Коффи объясняет, почему 31 используется под Сглаживание предвзятости.
В основном использование 31 дает более равномерное распределение вероятностей для хеш-функции.
Вы можете прочитать исходную аргументацию Блоха в разделе «Комментарии» в http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622. Он исследовал производительность различных хэш-функций в отношении результирующего «среднего размера цепочки» в хеш-таблице. P(31) был одной из распространенных функций в то время, которую он нашел в книге K&R (но даже Керниган и Ричи не могли вспомнить, откуда она взялась). В конце концов, ему пришлось выбрать один, и поэтому он взял P(31), так как он, казалось, работал достаточно хорошо. Несмотря на то, что P(33) был не хуже, а умножение на 33 вычисляется одинаково быстро (просто сдвиг на 5 и сложение), он выбрал 31, поскольку 33 не является простым:
Of the remaining four, I'd probably select P(31), as it's the cheapest to calculate on a RISC machine (because 31 is the difference of two powers of two). P(33) is similarly cheap to calculate, but it's performance is marginally worse, and 33 is composite, which makes me a bit nervous.
Таким образом, рассуждение было не таким рациональным, как предполагают многие из приведенных здесь ответов. Но мы все умеем придумывать рациональные причины после интуитивных решений (и даже Блох может быть к этому склонен).
Из JDK-4045622, где Джошуа Блох описывает причины, по которым была выбрана именно эта (новая) реализация String.hashCode().
The table below summarizes the performance of the various hash functions described above, for three data sets:
1) All of the words and phrases with entries in Merriam-Webster's 2nd Int'l Unabridged Dictionary (311,141 strings, avg length 10 chars).
2) All of the strings in /bin/, /usr/bin/, /usr/lib/, /usr/ucb/ and /usr/openwin/bin/* (66,304 strings, avg length 21 characters).
3) A list of URLs gathered by a web-crawler that ran for several hours last night (28,372 strings, avg length 49 characters).
The performance metric shown in the table is the "average chain size" over all elements in the hash table (i.e., the expected value of the number of key compares to look up an element).
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.2439Looking at this table, it's clear that all of the functions except for the current Java function and the two broken versions of Weinberger's function offer excellent, nearly indistinguishable performance. I strongly conjecture that this performance is essentially the "theoretical ideal", which is what you'd get if you used a true random number generator in place of a hash function.
I'd rule out the WAIS function as its specification contains pages of random numbers, and its performance is no better than any of the far simpler functions. Any of the remaining six functions seem like excellent choices, but we have to pick one. I suppose I'd rule out Vo's variant and Weinberger's function because of their added complexity, albeit minor. Of the remaining four, I'd probably select P(31), as it's the cheapest to calculate on a RISC machine (because 31 is the difference of two powers of two). P(33) is similarly cheap to calculate, but it's performance is marginally worse, and 33 is composite, which makes me a bit nervous.
Josh
В последней версии JDK по-прежнему используется 31. https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/String.html#hashCode ()
Хеш-строка предназначена для
^, помогает уникальный)31 - максимальное значение, которое можно поместить в 8-битный (= 1 байт) регистр, наибольшее простое число, которое можно поместить в 1-байтовый регистр, - нечетное число.
Умножьте 31 на << 5, затем вычтите само себя, поэтому нужны дешевые ресурсы.
Это потому, что 31 имеет приятное свойство - его умножение можно заменить побитовым сдвигом, который быстрее стандартного умножения:
31 * i == (i << 5) - i
Большое ожидание от хэш-функций заключается в том, что равномерная случайность их результата выдерживает такую операцию, как hash(x) % N, где N - произвольное число (и во многих случаях степень двойки), одна из причин заключается в том, что такие операции обычно используются в хеш-таблицах для определения слоты. Использование множителей простых чисел при вычислении хеша снижает вероятность того, что ваш множитель и N делят делители, что сделает результат операции менее случайным.
Другие указали на то замечательное свойство, что умножение на 31 может быть выполнено умножением и вычитанием. Я просто хочу указать, что для таких простых чисел существует математический термин: Мерсенн Прайм
Все простые числа Мерсенна на единицу меньше степени двойки, поэтому мы можем записать их как:
p = 2^n - 1
Умножая x на p:
x * p = x * (2^n - 1) = x * 2^n - x = (x << n) - x
Сдвиги (SAL / SHL) и вычитания (SUB) обычно выполняются быстрее, чем умножение (MUL) на многих машинах. См. таблицы инструкций от Agner Fog
Вот почему GCC, кажется, оптимизирует умножение на простые числа Мерсена, заменяя их сдвигами и подстановками, глянь сюда.
Однако, на мой взгляд, такое маленькое простое число - плохой выбор для хеш-функции. При относительно хорошей хеш-функции можно ожидать случайности в старших битах хеш-функции. Однако с хеш-функцией Java почти нет случайности в старших битах с более короткими строками (и все еще очень сомнительной случайности в младших битах). Это затрудняет создание эффективных хеш-таблиц. См. этот приятный трюк, который вы не смогли бы сделать с хеш-функцией Java.
В некоторых ответах упоминается, что, по их мнению, хорошо, что 31 помещается в байт. На самом деле это бесполезно, поскольку:
(1) Мы выполняем сдвиги вместо умножения, поэтому размер множителя не имеет значения.
(2) Насколько мне известно, нет специальной инструкции x86 для умножения 8-байтового значения на 1-байтовое значение, поэтому вам все равно пришлось бы преобразовать «31» в 8-байтовое значение, даже если вы умножали. Смотрите здесь, вы умножаете целые 64-битные регистры.
(А 127 на самом деле является самым большим простым числом Мерсенна, которое может уместиться в байте.)
Увеличивает ли меньшее значение случайность в средних и младших битах? Возможно, но это также, кажется, значительно увеличивает количество возможных столкновений :).
Можно перечислить много разных проблем, но обычно они сводятся к двум основным принципам, которые не выполняются должным образом: Путаница и Распространение
Но быстро ли это? Наверное, потому что мало что делает. Однако, если здесь в центре внимания производительность, один символ в цикле будет довольно неэффективным. Почему бы не делать 4 символа за раз (8 байтов) на итерацию цикла для более длинных строк, нравится? Что ж, это было бы сложно сделать с текущим определением хэша, где вам нужно умножать каждый символ индивидуально (пожалуйста, скажите мне, есть ли небольшой хак, чтобы решить эту проблему: D).
Сравните также stackoverflow.com/questions/1835976/… - я думаю, что 31 - плохой выбор, если вы пишете свои собственные хэш-функции.