Почему таблица хэша изменяется в два раза?

Проверка в java и googling онлайн для примеров кода hashtable кажется, что изменение размера таблицы выполняется путем удвоения. Но большинство учебников говорят, что лучший размер для таблицы - простое число.
Итак, мой вопрос:
Является ли подход удвоения, потому что:

  • Легко реализовать, или
  • Нахождение простого числа слишком неэффективно (но я считаю, что поиск следующее простое переключение n+=2 и тестирование на первичность с использованием modulo - O (loglogN), который является дешевым)
  • Или это мое недоразумение и только некоторые варианты хеш-таблицы требуется только размер основного стола?

Update:
Для работы определенных свойств требуется способ, представленный в учебниках с использованием простого числа (например, для квадратичного зондирования нужна таблица простого размера, чтобы доказать, что, например, если таблица не заполнена, элемент X будет вставлен). Ссылка, размещенная как повторяющаяся, обычно спрашивает об увеличении на любое число, например. 25% или следующий премьер, и принятый ответ утверждает, что мы удваиваем, чтобы сохранить операцию изменения размера "редкими", чтобы мы могли гарантировать амортизированное время.
Это не отвечает на вопрос о том, что размер таблицы является простым и с использованием простого для изменения размера, которое даже больше, чем двойное. Поэтому идея состоит в том, чтобы сохранить свойства основного размера с учетом накладных расходов на изменение размера

Ответ 1

Q: Но большинство учебников говорят, что лучший размер для таблицы - простое число.

Относительно размерности:

Что приходит к простоте размера, это зависит от выбранного вами алгоритма разрешения конфликтов. Некоторые алгоритмы требуют простого размера таблицы (двойное хеширование, квадратичное хеширование), другие - нет, и они могут извлечь выгоду из размера таблицы 2, поскольку это позволяет очень дешево работать по модулю. Однако, когда самые близкие "доступные размеры таблиц" отличаются в 2 раза, использование хэш-таблицы в памяти может быть ненадежным. Таким образом, даже используя линейное хеширование или отдельную цепочку, вы можете выбрать неэнергию 2-го размера. В этом случае, в свою очередь, стоит выбрать особый размер, потому что:

Если вы выберете размер первичной таблицы (либо потому, что алгоритм требует этого, либо потому, что вы не удовлетворены ненадежностью использования памяти, подразумеваемой величиной мощности 2), вычисление слота стола (по модулю по размеру таблицы) может быть объединено с хешированием. Подробнее см. .

Точка, в которой размер таблицы 2 не является нежелательным, когда распределение функции хеш-функции является плохим (из ответа Нила Коффи), нецелесообразно, потому что даже если у вас есть плохая хеш-функция, она откачивается и все еще использует power-of-2 размер будет быстрее, чем переход на основной размер таблицы, поскольку одно целое подразделение еще более медленнее на современных процессорах, чем несколько мультипликаций и операций сдвига, требуемых хорошими функциями лавинности, например. г. от MurmurHash3.


Q: Кроме того, честно говоря, я немного потерялся, если вы на самом деле рекомендуете простые слова или нет. Кажется, что это зависит от варианта хеш-таблицы и качества хэш-функции?

  • Качество хеш-функции не имеет значения, вы всегда можете "улучшить" хеш-функцию с помощью avarancing MurMur3, что дешевле, чем переключение на размер основного стола из размера таблицы "power-of-2", см. выше.

  • Я рекомендую выбирать простой размер с помощью алгоритма QHash или квадратичного хэша (не совпадают), только когда вам нужно точный контроль над коэффициентом нагрузки хеш-таблицы и прогнозируемо высокими фактическими нагрузками. При использовании размера таблицы "размер-2" минимальный коэффициент изменения размера равен 2, и, как правило, мы не можем гарантировать, что хеш-таблица будет иметь фактический коэффициент загрузки, превышающий 0,5. См. этот ответ.

    В противном случае я рекомендую перейти с хэш-таблицей с силовым размером 2 с линейным зондированием.

Q: Является ли подход удвоения, потому что:
Его легко реализовать, или

В принципе, во многих случаях да. См. этот большой ответ относительно факторов загрузки:

Коэффициент загрузки не является существенной частью структуры данных хеш-таблицы - это способ определения правил поведения для динамической системы (растущая/сокращающаяся хеш-таблица является динамической системой).

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

Что удваивается? Это просто самая простая стратегия изменения размера. Стратегия может быть произвольно сложной, оптимально выполняя свои варианты использования. Он мог бы учитывать текущий размер хэш-таблицы, интенсивность роста (сколько операций было выполнено с предыдущего изменения размера) и т.д. Никто не запрещает вам реализовывать такую ​​настраиваемую логику изменения размера.

Q: Нахождение простого числа слишком неэффективно (но я думаю, что поиск следующего простого перехода по n + = 2 и тестирование для primality с использованием modulo - это O (loglogN), который является дешевым)

Существует хорошая практика прекомпретации некоторого подмножества основных размеров хэш-таблицы, чтобы выбирать между ними, используя двоичный поиск во время выполнения. См. список с двумя значениями хэша и пояснения, Возможности QHash. Или, даже используя прямой поиск, это очень быстро.

Q: Или это мое недоразумение, и только некоторые варианты хеш-таблицы требуют только размера основной таблицы?

Да, только определенные типы requre, см. выше.

Ответ 2

Java HashMap (java.util.HashMap) цепочки кодовых столкновений в связанном списке (или [в зависимости от JDK8] дерева в зависимости от размера и переполнения бункеров).

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

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

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

Обратите внимание, что при использовании полномочий двух повторных операций в два раза размер "разделяет" каждое ведро между двумя ведрами на основе "следующего" бита хеш-кода. То есть, если хэш-таблица имела 256 ведер, и поэтому использование младших 8 бит перехвата хэш-значения разбивает каждую цепочку столкновений на основе 9-го бита и либо остается в одном ведре B (9-й бит равен 0), либо идет в bucket B + 256 (9-й бит равен 1). Такое расщепление может сохранить/использовать подход к управлению ковшом. Например, java.util.HashMap хранит небольшие ведра, отсортированные в обратном порядке вставки, а затем разбивает их на две подструктуры, подчиняющиеся этому порядку. Он хранит большие ведра в двоичном дереве, отсортированном по хэш-коду и аналогичным образом разбивая дерево, чтобы сохранить этот порядок.

NB: Эти трюки не были реализованы до JDK8.

(я уверен) java.util.HashMap только размеры вверх (никогда не вниз). Но есть аналогичная эффективность для сокращения вдвое таблицы хешей как удвоения.

Один из недостатков этой стратегии заключается в том, что разработчикам Object явно не требуется, чтобы убедиться, что бит младшего порядка хэш-кодов хорошо распределен. Совершенно допустимый хеш-код может быть хорошо распределен в целом, но плохо распределен в его младших битах. Таким образом, объект, подчиняющийся генеральному контракту для hashCode(), может по-прежнему находиться в цистерне, когда фактически используется в HashMap! java.util.HashMap смягчает это, применяя дополнительный хэш-спред 'к предоставленной реализации hashCode(). Это "распространение" на самом деле очень грубо (xors 16 бит с низким).

Объект-импламенты должны знать (если не уже), что смещение в их хэш-коде (или его отсутствие) может оказать значительное влияние на производительность структур данных с использованием хэшей.

Для записи я основал этот анализ на этой копии источника:

http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/HashMap.java