Чому хеш-функції повинні використовувати модуль простого числа?


335

Дуже давно я придбав книгу структур даних за ціною торгів за 1,25 долара. У ньому пояснення хеширующей функції говорило про те, що в кінцевому підсумку воно повинно бути модифікованим простим числом через "природу математики".

Що ви очікуєте від книги в розмірі 1,25 долара?

У всякому разі, у мене були роки, щоб думати про природу математики, і досі не можу це зрозуміти.

Чи розподіл чисел справді більше, навіть якщо є проста кількість відро? Або це стара казка програміста, яку приймають усі, тому що всі інші її приймають?


1
Ідеально розумне запитання: Чому повинна бути проста кількість відра?
Draemon

1
Це питання, здається, поза темою, оскільки воно, швидше за все, належить до інформатики .
Гонки легкості по орбіті

2
cs.stackexchange.com/a/64191/64222 ще одне добре аргументоване пояснення.
Зелене дерево


Ось ще одне чудове пояснення дещо пов’язаного запитання з деякими вражаючими доказовими номерами - quora.com/…
AnBisw

Відповіді:


242

Зазвичай проста хеш-функція працює, приймаючи "складові частини" вхідних даних (символи у випадку рядка) та множуючи їх на сили деякої константи та додаючи їх разом у якийсь цілий тип. Так, наприклад, типовий (хоча і не особливо хороший) хеш рядка може бути:

(first char) + k * (second char) + k^2 * (third char) + ...

Тоді, якщо введено купу рядків, які мають один і той же перший знак, то всі результати будуть однаковими по модулю k, принаймні до тих пір, поки цілий тип не переповниться.

[Як приклад, строковий хеш-код Java на явно схожий на це - він робить символи у зворотному порядку, з k = 31. Таким чином, ви отримуєте яскраві зв'язки по модулю 31 між рядками, які закінчуються однаково, і яскраві зв'язки по модулю 2 ^ 32 між рядками, які однакові, за винятком кінця. Це серйозно не зіпсує поведінку хештеля.]

Хештеб працює, приймаючи модуль хеша на кількість відра.

У хештелі важливо не створювати зіткнень для ймовірних випадків, оскільки зіткнення знижують ефективність хештеля.

Тепер, припустимо, хтось вкладає цілу купу значень у хешблет, які мають певні стосунки між елементами, як і всі, які мають однаковий перший символ. Я б сказав, що це досить передбачувана схема використання, тому ми не хочемо, щоб він спричинив занадто багато зіткнень.

Виявляється, "через природу математики", якщо константа, яка використовується в хеші, і кількість відра, є спільними , то в деяких поширених випадках зіткнення мінімізуються. Якщо вони не є копром, то існують деякі досить прості відносини між входами, для яких зіткнення не зведені до мінімуму. Усі хеші виходять рівним за модулем загальним коефіцієнтом, а значить, всі вони потраплятимуть на 1 / n-ту відра, які мають це значення за модулем загальним фактором. Ви отримуєте n разів більше зіткнень, де n - загальний фактор. Оскільки n становить щонайменше 2, я б сказав, що для досить простого випадку використання неприпустимо генерувати принаймні вдвічі більше зіткнень, ніж зазвичай. Якщо якийсь користувач збирається розбити наш дистриб'ютор у відрах, ми хочемо, щоб це була випадкова випадковість, а не просте передбачуване використання.

Тепер реалізація хештету очевидно не має контролю над елементами, що вкладаються до них. Вони не можуть перешкодити їм бути пов’язаними. Тож потрібно зробити так, щоб константа та кількість відра були одночасно. Таким чином, ви не покладаєтесь лише на "останній" компонент, щоб визначити модуль відра стосовно деякого невеликого загального чинника. Наскільки я знаю, вони не повинні бути прем'єр-міністром, щоб досягти цього, лише копром.

Але якщо хеш-функція та хештел записуються незалежно, то хештил не знає, як працює хеш-функція. Це може бути використання константи з малими факторами. Якщо вам пощастить, це може працювати зовсім інакше і бути нелінійним. Якщо хеш досить хороший, то будь-яке кількість відра просто чудово. Але параноїдальний хештель не може брати на себе хорошу хеш-функцію, тому слід використовувати просту кількість відро. Аналогічно, параноїдальна хеш-функція повинна використовувати велику первинну константу, щоб зменшити ймовірність того, що хтось використовує ряд відра, які, мабуть, мають спільний фактор з постійною.

На практиці я думаю, що цілком нормально використовувати потужність 2 як кількість відра. Це зручно і економить необхідність пошуку або попереднього вибору простого числа потрібної величини. Тож ви покладаєтесь на хеш-функцію не використовувати навіть множників, що, як правило, є безпечним припущенням. Але ви все ще можете отримувати випадкові поводження з хешируванням на основі хеш-функцій, таких як вище, і кількість простих відра може допомогти надалі.

Якщо говорити про принцип, що "все має бути першочерговим", наскільки я знаю, є достатньою, але не необхідною умовою для хорошого розподілу хештелів. Це дозволяє всім взаємодіяти без необхідності припускати, що інші дотримуються того самого правила.

[Редагувати: є ще одна, більш спеціалізована причина використання простої кількості відра, тобто якщо ви керуєтесь зіткненнями з лінійним зондуванням. Тоді ви обчислюєте хід з хеш-коду, і якщо цей крок виявляється фактором кількості відра, то ви можете робити зонди (bucket_count / stride) зонди, перш ніж ви повернетесь з того місця, де ви почали. Випадки, яких ви найбільше хочете уникнути, - це stride = 0, звичайно, який повинен бути спеціалізованим, але щоб уникнути також bucket_count / stride, котрий має спеціальний об'єм, рівний малому цілому числу, ви можете просто зробити bucket_count prime і не байдуже, що кроку за умови, що це не 0.]


Як бічна примітка: дискусія щодо розумного вибору коефіцієнта k для hashCodes є тут: stackoverflow.com/q/1835976/21499
Hans-Peter Störr

9
це приголомшлива відповідь. Ви можете пояснити це далі "Отже, ви отримуєте яскраві відносини по модулю 31 між рядками, які закінчуються однаково, і яскраві зв'язки по модулю 2 ^ 32 між рядками, які однакові, крім кінця. Це серйозно не зіпсує поведінку хештелю. " Я особливо не розумію 2 ^ 32 частини
звичайну

2
Додаткове зауваження, щоб зробити це більш зрозумілим з цього приводу: "Усі хеші виходять рівними по модулю загальним фактором" -> Це тому, що, якщо розглядати приклад хеш-функції хеш = 1-й знак + 2-й char * k + ..., і взяти рядки з тим самим першим символом, хеш% k буде однаковим для цих рядків. Якщо M - розмір хештибу, а g - gcd M і k, то (хеш% k)% g дорівнює хеш% g (оскільки g ділить k), а отже, хеш% g також буде однаковим для цих рядків. Тепер розглянемо (хеш% M)% g, це дорівнює хеш% g (оскільки g ділить M). Отже (хеш% M)% g дорівнює для всіх цих рядків.
Кварк

1
@DanielMcLaury Джошуа Блох пояснив, чому для Java - це було рекомендовано у двох популярних книгах (K&R, Dragon book) і добре справлялося з низькими зіткненнями в словнику англійської мови. Це швидко (використовує метод Хорнера ). Мабуть, навіть K&R не пам'ятає, звідки це взялося. Аналогічна функція Рабина відбитків пальців від Рабина-Карпа алгоритму (1981) , але K & R (1978) ще до того, що.
bain

1
@SteveJessop, будь ласка, чи можете ви пояснити "вражаючі відносини по модулю 2 ^ 32 між рядками, які однакові, за винятком кінця"? Дякую.
Khanna111

29

Перше, що ви робите, вставляючи / витягуючи з хеш-таблиці, - це обчислити хеш-код для даного ключа, а потім знайти правильне відро, обрізавши хеш-код до розміру хеш-таблиці, виконуючи hashCode% table_length. Ось 2 "заяви", які ви, ймовірно, десь читали

  1. Якщо ви використовуєте потужність 2 для таблиці_length, пошук (hashCode (ключ)% 2 ^ n) такий же простий і швидкий, як (hashCode (ключ) & (2 ^ n -1)). Але якщо ваша функція обчислення хеш-коду для даного ключа не є хорошою, ви, безумовно, постраждаєте від кластеризації багатьох ключів у декількох хеш-відрах.
  2. Але якщо ви використовуєте прості числа для table_length, обчислені хеш-коди можуть відображатись у різних хеш-відрах, навіть якщо у вас є трохи дурна функція hashCode.

І ось доказ.

Якщо припустимо, що ваша функція hashCode призводить до наступних хеш-кодів серед інших {x, 2x, 3x, 4x, 5x, 6x ...}, то все це буде кластеризовано лише в m кількості відро, де m = table_length / GreatestCommonFactor (довжина_ таблиця, х). (Це банально перевірити / отримати це). Тепер ви можете зробити одне з наступних, щоб уникнути кластеризації

Переконайтеся, що ви не генеруєте занадто багато хеш-кодів, кратних іншому хеш-коду, як у {x, 2x, 3x, 4x, 5x, 6x ...}. Але це може бути складним, якщо у вашому хеш-таблиці має бути мільйони записів. Або просто зробіть m рівним довжиною table_length, зробивши GreatestCommonFactor (table_length, x) рівним 1, тобто, зробивши co_prd довжини table_le з x. І якщо х може бути майже будь-яке число, то переконайтеся, що довжина_просторола є простим числом.

Від - http://srinvis.blogspot.com/2006/07/hash-table-lengths-and-prime-numbers.html


11

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/

Досить чітке пояснення, із зображеннями теж.

Редагувати: Як підсумок, використовуються праймери, оскільки ви маєте найкращі шанси отримати унікальне значення при множенні значень на вибране просте число та додавання їх до всього. Наприклад, заданий рядок, помноживши кожну буквену величину з простим числом, а потім додавши ці всі, дасть вам хеш-значення.

Краще питання було б, чому саме число 31?


5
Хоча, я думаю, резюме було б корисним, якщо цей сайт коли-небудь загине, частина залишків його вмісту буде збережена тут на SO.
Томас Оуенс

2
Стаття не пояснює, чому, але говорить: "Дослідники виявили, що використання простирадла 31 дає кращий розподіл ключів, а менша кількість зіткнень. Ніхто не знає чому ..." Смішно, задаючи те саме питання, що і я у дійсності .
theschmitzer

> Краще питання було б, чому саме число 31? Якщо ви маєте на увазі, чому використовується число 31, то стаття, яку ви вказуєте, говорить вам про те, чому це означає, що це тест, який швидко перетворюється на багато, і тому, що тести cos показують, що це найкраще для використання. Інший популярний множник, який я бачив, - це 33, який надає ваги теорії про те, що питання швидкості був (принаймні спочатку) важливим фактором. Якщо ви маєте на увазі, що це за 31 рік, що робить його краще в тестах, то, боюся, я не знаю.
sgmoore

Саме так, єдиною причиною того, що його можна було використовувати як множник, було те, що його було легко помножити на. (Коли я кажу, що я бачив 33, які використовуються як множник, я не маю на увазі останнім часом, це, мабуть, було десятиліть тому, і це можливо, перш ніж було зроблено багато аналізу на хешування).
sgmoore

3
@SteveJessop Число 31 легко оптимізується процесором як операція (x * 32) -1, в якій *32простий бітовий зсув, а ще краще коефіцієнт масштабу прямої адреси (наприклад, lea eax,eax*8; leax, eax,eax*4на x86 / x64). Так що *31це хороший кандидат для множення простих чисел. Це було майже справді кілька років тому - тепер останні архітектури процесорів мають майже миттєве множення - поділ завжди повільніше ...
Арно Бушез

10

тл; д-р

index[hash(input)%2]це призведе до зіткнення половини всіх можливих хешів та діапазону значень. index[hash(input)%prime]призводить до зіткнення <2 усіх можливих хешів. Закріплення дільника на розмірі таблиці також гарантує, що число не може бути більшим за таблицю.


1
2 - чувак з простого числа
Ганеш Чоудхарі Саданала

8

Прайми використовуються тому, що у вас є шанси отримати унікальне значення для типової хеш-функції, яка використовує поліноми модуля P. Скажімо, ви використовуєте таку хеш-функцію для рядків довжиною <= N, і у вас зіткнення. Це означає, що 2 різних многочлена виробляють однакове значення за модулем P. Різниця цих многочленів знову є многочленом того ж ступеня N (або меншим). Він має не більше N коренів (саме тут проявляється природа математики, оскільки це твердження справедливо лише для полінома над полем => простим числом). Отже, якщо N набагато менший за Р, ви, швидше за все, не матимете зіткнення. Після цього експеримент, ймовірно, може показати, що 37 досить великий, щоб уникнути зіткнень для хеш-таблиці рядків, що мають довжину 5-10, і достатньо малий, щоб використовувати його для обчислень.


1
Хоча пояснення здається очевидним, воно дійшло до мене, прочитавши книгу А.Шена "Програмування: теореми та проблеми" (російською мовою), див. Обговорення алгоритму Рабіна. Не впевнений, чи існує англійський переклад.
TT_

5

Просто для надання альтернативної точки огляду є цей сайт:

http://www.codexon.com/posts/hash-functions-the-modulo-prime-myth

Що стверджує, що ви повинні використовувати якомога більшу кількість відра, а не округлення до простої кількості відра. Це здається розумною можливістю. Інтуїтивно зрозуміло, що я, безумовно, бачу, як більша кількість відра було б краще, але я не можу зробити математичний аргумент цього.


Більша кількість відра означає менше зіткнень: Дивіться принцип голубої лунки.
Невідомо

11
@ Невідомо: Я не вірю, що це правда. Будь ласка, виправте мене, якщо я помиляюся, але я вважаю, що застосування принципу «голубині дуги» до хеш-таблиць дозволяє лише стверджувати, що НЕ буде зіткнень, якщо у вас більше елементів, ніж у бункерах, не робити жодних висновків щодо кількості чи щільності зіткнень. Я все ще вважаю, що більша кількість бункерів є правильним маршрутом.
Фалайна

Якщо ви припускаєте, що зіткнення для всіх намірів і цілей є випадковими, то, парадокс дня народження, більший простір (відра) зменшить ймовірність виникнення зіткнення.
Невідомо

1
@ Невідомо ви пропустили, що зіткнення залежать і від самої хеш-функції. Тож якщо функція has дійсно погана, то незалежно від того, наскільки сильно ви збільшуєте розмір, все-таки може виникнути значна кількість зіткнень
Suraj Chandran

Оригінальну статтю, здається, немає, але тут є кілька проникливих коментарів, зокрема дискусія з оригінальним автором. news.ycombinator.com/item?id=650487
Адріан Маккарті

3

Прайми - це унікальні цифри. Вони унікальні тим, що твір прем’єра з будь-яким іншим числом має найкращі шанси бути унікальним (не таким унікальним, як сам прем'єр-мінімум) завдяки тому, що для його складання використовується прайм. Ця властивість використовується у хеш-функціях.

Давши рядок "Samuel", ви можете створити унікальний хеш, помноживши кожну із складових цифр або букв на просте число і додавши їх. Саме тому використовуються праймери.

Однак використання праймерів - стара технологія. Тут важливо зрозуміти, що поки ви зможете створити достатньо унікальний ключ, ви також можете перейти до інших методів хешування. Перейдіть сюди, щоб дізнатися більше про цю тему про http://www.azillionmonkeys.com/qed/hash.html

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/


1
hahahah .... насправді, чи не є продукт двох простих шансів бути "унікальним", ніж продукт простий і будь-який інший номер?
HasaniH

@Beska Тут "унікальність" визначається рекурсивно, тому я вважаю, що "унікальність" слід визначати так само :)
TT_

3

Це залежить від вибору хеш-функції.

Багато хеш-функцій поєднують різні елементи в даних, помножуючи їх на деякі коефіцієнти, модулюючи потужність двох, що відповідають розміру слова машини (цей модуль вільний, просто даючи перерахунок перерахунку).

Ви не хочете, щоб якийсь спільний множник між множником для елемента даних та розміром хеш-таблиці, оскільки тоді може статися, що зміна елемента даних не поширюватиме дані по всій таблиці. Якщо ви обираєте основний розмір таблиці, такий загальний фактор є малоймовірним.

З іншого боку, ці фактори зазвичай складаються з непарних простих ліній, тому ви також повинні бути безпечними, використовуючи потужності двох для вашої хеш-таблиці (наприклад, Eclipse використовує 31, коли він генерує метод Java hashCode ()).


2

Припустимо, розмір таблиці (або число за модулем) T = (B * C). Тепер, якщо хеш для вашого вводу такий, як (N * A * B), де N може бути будь-яким цілим числом, то ваш вихід не буде добре розподілений. Оскільки щоразу, коли n стає C, 2C, 3C тощо, ваш вихід почне повторюватися. тобто ваш вихід буде розподілений тільки в C позиціях. Зауважте, що C тут є (T / HCF (розмір таблиці, хеш)).

Цю проблему можна усунути, створивши HCF 1. Прості номери для цього дуже хороші.

Ще одна цікава річ, коли T дорівнює 2 ^ N. Вони дадуть вихід точно такий же, як і всі нижні N бітів вхідного хеша. Оскільки кожне число може бути представлене потужністю 2, коли ми візьмемо модуль будь-якого числа з T, ми віднімемо всі сили числа 2 форми, які є> = N, отже, завжди видаючи кількість певного шаблону, залежно від введення . Це теж поганий вибір.

Аналогічно, T як 10 ^ N є поганим також через подібні причини (шаблон у десятковому позначенні чисел замість двійкових).

Отже, прості числа, як правило, дають кращі розподілені результати, отже, хороший вибір для розміру таблиці.


2

Копіювання з моєї іншої відповіді https://stackoverflow.com/a/43126969/917428 . Детальніше та приклади див.

Я вважаю, що це просто пов'язане з тим, що комп'ютери працюють з базою 2. Подумайте, як те саме працює для бази 10:

  • 8% 10 = 8
  • 18% 10 = 8
  • 87865378% 10 = 8

Не має значення, яке число: доки він закінчується на 8, його модуль 10 буде 8.

Вибір достатньо великого числа, що не має потужності, забезпечить хеш-функцію дійсно функцією всіх вхідних бітів, а не їх підмножини.


1

Я хотів би додати щось для відповіді Стіва Джессопа (я не можу це коментувати, оскільки мені не вистачає репутації). Але я знайшов якийсь корисний матеріал. Його відповідь дуже допомагає, але він помилився: розмір відра не повинен бути потужністю 2. Я просто цитую книгу "Вступ до алгоритму" Томаса Кормена, Чарльза Лейзерсена та ін на сторінці263:

Використовуючи метод поділу, ми зазвичай уникаємо певних значень m. Наприклад, m не повинна бути потужністю 2, оскільки якщо m = 2 ^ p, то h (k) - це просто p бітів нижнього порядку k. Якщо ми не знаємо, що всі бітові шаблони низького порядку однаково вірогідні, нам краще розробити хеш-функцію залежно від усіх бітів ключа. Оскільки вправа 11.3-3 просить показати, вибір m = 2 ^ p-1, коли k - символьна рядок, інтерпретована в radix 2 ^ p, може бути поганим вибором, тому що перестановка символів k не змінює її хеш-значення.

Сподіваюся, це допомагає.


0

Для хеш-функції важливо не тільки звести до мінімуму колізії, але й унеможливити перебування з тим самим хешем, змінюючи кілька байтів.

Скажіть, у вас є рівняння: (x + y*z) % key = xз 0<x<keyі 0<z<key. Якщо ключ - це первинне число n * y = ключ, то вірно для кожного n у N та false для кожного іншого числа.

Приклад, коли ключ не є простим прикладом: x = 1, z = 2 і key = 8 Оскільки ключ / z = 4 все ще є натуральним числом, 4 стає рішенням для нашого рівняння і в цьому випадку (n / 2) * y = ключ справедливий для кожного n в N. Кількість розв’язків рівняння практично подвоїлася, оскільки 8 не є простим.

Якщо наш зловмисник вже знає, що 8 можливе рішення рівняння, він може змінити файл з 8 на 4 і все одно отримає той самий хеш.


0

Я читав популярний веб-сайт Wordpress, пов’язаний з деякими з вищезазначених популярних відповідей вгорі. З того, що я зрозумів, я хотів би поділитися простим спостереженням, яке я зробив.

Ви можете знайти всі деталі у статті тут , але припустимо, що це справедливо:

  • Використання простого числа дає нам "найкращий шанс" унікальної цінності

Загальна реалізація хешмапу хоче, щоб дві речі були унікальними.

  • Унікальний хеш-код для ключа
  • Унікальний індекс для збереження фактичного значення

Як ми отримуємо унікальний індекс? Зробивши початковий розмір внутрішнього контейнера також основним. В основному, прайм бере участь, оскільки він володіє цією унікальною ознакою створення унікальних чисел, які ми в кінцевому підсумку використовуємо для ідентифікації об'єктів та пошуку індексів всередині внутрішнього контейнера.

Приклад:

key = "ключ"

value = "значення" uniqueId = "k" * 31 ^ 2 + "e" * 31 ^ 1` + "y"

карти на унікальний ідентифікатор

Тепер ми хочемо унікальне місце для нашої цінності - так і ми

uniqueId % internalContainerSize == uniqueLocationForValue, припускаючи, що internalContainerSizeце також прем'єр.

Я знаю, що це спрощено, але я сподіваюся отримати загальне уявлення.


0

"Характер математики" щодо простих модулів потужності полягає в тому, що вони є одним будівельним блоком кінцевого поля . Інші два будівельні блоки - це операція додавання та множення. Особливістю простих модулів є те, що вони утворюють скінченне поле з "регулярними" операціями додавання та множення, щойно взятими за модуль. Це означає, що кожне множення відображає на різний цілий модуль простим, так само як і кожне додавання.

Основні модулі вигідні тим, що:

  • Вони дають найбільшу свободу при виборі вторинного множника у вторинному хешировании, всі множники, крім 0, в кінцевому підсумку відвідують усі елементи точно один раз
  • Якщо всіх хешей менше за модулем, зіткнень взагалі не буде
  • Випадкові прайми краще змішуються, ніж потужність двох модулів, і стискають інформацію про всі біти, а не лише підмножину

Однак у них є великий мінус, їм потрібен цілий поділ, який займає багато (~ 15-40) циклів, навіть у сучасному процесорі. Маючи приблизно половину обчислень, можна переконатися, що хеш дуже добре перемішаний. Два операції по множенню та операції з переміщенням змішуватимуться краще, ніж основний мудул. Тоді ми можемо використовувати будь-який розмір хеш-таблиці, а скорочення хешу є найшвидшим, даючи 7 операцій загалом для потужності 2 розмірів таблиці та близько 9 операцій для довільних розмірів.

Нещодавно я переглянув багато найшвидших реалізацій хеш-таблиць, і більшість з них не використовують прості модулі.


0

Це питання було об'єднано з більш підходящим питанням, чому хеш-таблиці повинні використовувати масиви простих розмірів, а не потужність 2. Для самих хеш-функцій тут є багато хороших відповідей, але на відповідне питання, чому деякі критичні хеш-хеш-таблиці як-от glibc, використовують масиви великих розмірів, поки немає.

Взагалі потужність 2-х столів набагато швидша. Там дорогий h % n => h & bitmask, де біт-маску можна обчислити через clz("рахувати провідні нулі") розміром n. Функція модуля повинна робити ціле ділення, яке приблизно на 50 разів повільніше, ніж логічне and. Існує кілька хитрощів, як уникнути модуля, наприклад, використовуючи https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-mod-reduction/ Lemire/ , але, як правило, швидкі хеш-таблиці використовують енергію з 2, а захищені хеш-таблиці використовують праймери.

Чому так?

Безпека в цьому випадку визначається атаками на стратегію розв'язання зіткнень, яка у більшості хеш-таблиць є просто лінійним пошуком у пов'язаному списку зіткнень. Або при більш швидкому пошуку ліній у прямому пошуку в таблиці. Таким чином, з потужністю 2 таблиці та деякими внутрішніми знаннями таблиці, наприклад, розміром чи порядком списку клавіш, наданим деяким інтерфейсом JSON, ви отримуєте кількість використаних правильних біт. Кількість на бітмасі. Зазвичай це менше 10 біт. А для 5-10 біт тривіально жорстокі зіткнення навіть з найсильнішими і повільними хеш-функціями. Ви більше не отримуєте повну безпеку своїх 32-бітових або 64-бітних хеш-функцій. І справа в тому, щоб використовувати швидкі маленькі хеш-функції, а не монстри, такі як шум або навіть сифаш.

Отже, якщо ви надаєте зовнішній інтерфейс до вашої хеш-таблиці, як-от DNS-розв’язник, мова програмування, ... ви хочете дбати про зловживання людьми, які люблять DOS такими службами. Зазвичай таким людям простіше закрити вашу службу набагато простішими методами, але це сталося. Тож люди дбали.

Тож найкращі варіанти запобігання від таких атак зіткнення - це будь-який

1) використовувати прості таблиці, тому що тоді

  • всі 32 або 64 біти доречні для пошуку відра, а не лише декілька.
  • Функція зміни розміру хеш-таблиці є більш природною, ніж просто подвійна. Найкраща функція росту - це послідовність поля, і прайми наближаються до того, ніж подвоєння.

2) використовувати кращі заходи проти фактичної атаки разом із швидкою потужністю 2 розміру.

  • підраховують зіткнення і переривають або сплять на виявлених атаках, що є числами зіткнення з ймовірністю <1%. Як 100 з 32-бітовими хеш-таблицями. Це те, що, наприклад, робить djb's dns resolutionver.
  • перетворити пов'язаний список зіткнень у дерево з O (log n) пошуку не O (n), коли виявлена ​​атака зіткнення. Це те, що робить, наприклад, java.

Існує широкий міф про те, що більш захищені хеш-функції допомагають запобігти подібним атакам, що неправильно, як я пояснив. Немає безпеки лише з низькими бітами. Це працювало б лише з таблицями розміру найвищого розміру, але це використовувало б поєднання двох найповільніших методів, повільного хешування та повільного основного модуля.

Функції хешу для хеш-таблиць насамперед повинні бути невеликими (щоб бути незрозумілими) та швидкими. Безпека може виходити лише від запобігання лінійного пошуку в зіткненнях. І не використовувати тривіально погані хеш-функції, як такі, що не чутливі до деяких значень (наприклад, \ 0 при використанні множення).

Використання випадкових насінин також є хорошим варіантом, люди почали з цього спочатку, але, маючи достатньо інформації таблиці, навіть випадкове насіння не дуже допомагає, а динамічні мови, як правило, банально отримують насіння за допомогою інших методів, як це зберігається в відомі місця пам'яті.


-1
function eratosthenes(n) {

    function getPrime(x) {
        var middle = (x-(x%2))/2;
        var arr_rest = [];
        for(var j=2 ; j<=middle;j++){
            arr_rest.push(x%j);
        }

        if(arr_rest.indexOf(0) == -1) {
            return true
        }else {
            return false
        }

    }
    if(n<2)  {
        return []
    }else if(n==2){
        return [2]
    }else {
        var arr = [2]
        for(var i=3;i<n;i++) {
            if(getPrime(i)){
                arr.push(i)
            }
        }
    }

    return arr;
}

2
Чи можете ви додати коментарі, щоб пояснити своє рішення?
pom421
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.