Яке значення коефіцієнта навантаження в HashMap?


232

HashMapмає дві важливі властивості: sizeі load factor. Я переглянув документацію Java, і вона говорить 0.75fпро початковий коефіцієнт навантаження. Але я не можу знайти реальне використання цього.

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

Відповіді:


266

Документації пояснює це досить добре:

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

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

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


14
Інші відповіді припускають вказати , capacity = N/0.75щоб уникнути Rehashing, але моя початкова ідея була просто встановити load factor = 1. Чи будуть недоліки такого підходу? Чому впливає фактор навантаження get()та put()витрати на експлуатацію?
суперміт

19
Коефіцієнт навантаження = 1 хеш-карта з кількістю записів = місткість статистично матиме значну кількість зіткнень (= коли кілька ключів створюють один і той же хеш). При зіткненні час пошуку збільшується, оскільки в одному відрі буде> 1 відповідні записи, для яких ключ повинен бути індивідуально перевірений на рівність. Деякі детальні математики: preshing.com/20110504/hash-collision-probables
atimb

8
Я не слідкую за тобою @atimb; Властивість набору даних використовується лише для того, щоб визначити, коли збільшити розмір пам’яті правильно? - Як би навантаження на один збільшило ймовірність хеш-зіткнень? - Алгоритм хешування не знає, скільки предметів є на карті або як часто він набуває нових "відра" для зберігання тощо. Для будь-якого набору об'єктів однакового розміру, незалежно від того, як вони зберігаються, ви повинні мати однакова ймовірність повторних хеш-значень ...
BrainSlugs83

19
Ймовірність зіткнення хешу менша, якщо розмір карти більший. Наприклад, елементи з хеш-кодами 4, 8, 16 і 32 будуть розміщені в одному відрі, якщо розмір карти становить 4, але кожен елемент отримає власне відро, якщо розмір карти більше 32. Карта з початковим розміром 4 і коефіцієнтом навантаження 1,0 (4 відра, але всі 4 елементи в одному відрі) буде в цьому прикладі в середньому в два рази повільніше, ніж інша з коефіцієнтом навантаження 0,75 (8 відра, два відра заповнені - з елементом "4" та з елементами "8", "16", "32").
30-го

1
@Adelin Lookup збільшується для більш високих коефіцієнтів навантаження, оскільки буде більше зіткнень для більш високих значень, а спосіб, коли Java обробляє зіткнення, полягає в тому, щоб помістити елементи з тим же хеш-кодом в одне відро, використовуючи структуру даних. Починаючи з Java 8, ця структура даних є двійковим деревом пошуку. Це робить пошук в найгіршому випадку складним часом O (lg (n)) з найгіршим випадком, що трапляється, якщо всі додані елементи мають однаковий хеш-код.
Gigi Bayte 2

141

За замовчуванням початкова потужність HashMapзнімань становить 16, а коефіцієнт завантаження - 0,75f (тобто 75% від поточного розміру карти). Коефіцієнт навантаження представляє, на якому рівні HashMapмає бути збільшена потужність.

Наприклад добуток потужності та коефіцієнта навантаження як 16 * 0.75 = 12. Це означає, що після зберігання 12-ї пари ключ - значення HashMap, її ємність стає 32.


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

це означає, що кількість відра збільшується на 2?
LoveMeow

39

Власне, з моїх розрахунків, "ідеальний" коефіцієнт навантаження ближче до log 2 (~ 0,7). Хоча будь-який коефіцієнт навантаження менший від цього дасть кращі показники. Я думаю, що .75, мабуть, витягнули з шапки.

Доказ:

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

Нехай s представляє розмір і n кількість доданих клавіш. Використовуючи біноміальну теорему, ймовірність порожнього відра дорівнює:

P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)

Таким чином, відро, ймовірно, порожнє, якщо їх менше

log(2)/log(s/(s - 1)) keys

Коли s досягає нескінченності і якщо кількість доданих ключів така, що P (0) = .5, то n / s швидко наближається до журналу (2):

lim (log(2)/log(s/(s - 1)))/s as s -> infinity = log(2) ~ 0.693...

4
Математичні ботаніки FTW! Ймовірно, .75округлений до найближчої легко зрозуміти дріб log(2), і виглядає менш магічним числом. Я хотів би побачити оновлення до значення за замовчуванням JDK, із зазначенням коментаря над його реалізацією: D
Розшифроване

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

28

Що таке коефіцієнт навантаження?

Кількість ємності, яку HashMap має вичерпати, щоб збільшити її потужність?

Чому коефіцієнт навантаження?

Коефіцієнт завантаження за замовчуванням становить 0,75 від початкової ємності (16), тому 25% відра будуть вільними до збільшення ємності, і це робить багато нових відра з новими хеш-кодами, що вказують на їх існування відразу після збільшення кількість відра.

Тепер чому ви повинні зберігати багато безкоштовних відра і який вплив збереження вільних відра на продуктивність?

Якщо встановити коефіцієнт завантаження на рівні 1,0, то може статися щось дуже цікаве.

Скажімо, ви додаєте об’єкт x до своєї хешмапу, чий хеш-код 888, а у вашій хешмапі відро, що представляє хеш-код, вільний, тому об’єкт x додається до відра, але тепер знову скажіть, якщо ви додаєте інший об'єкт y, чий hashCode є також 888, то ваш об'єкт y буде доданий напевно, АЛЕ в кінці відра ( адже відра - це не що інше, як ключ, що зберігає впроваджений список списку, значення та наступне ), тепер це впливає на продуктивність! Оскільки ваш об'єкт y більше не присутній у головці відра, якщо ви здійснюєте пошук, час, відведений, не буде O (1)цього разу це залежить від того, скільки предметів є в одному відрі. Це називається хеш-зіткненням до речі & це навіть відбувається, коли ваш коефіцієнт завантаження менше 1.

Кореляція між продуктивністю, хеш-зіткненням та коефіцієнтом завантаження?

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

Виправте мене, якщо я десь помиляюся.


2
Ви можете додати трохи про те, як стримується хеш-код до числа з діапазоном 1- {count bucket}, і це не є певним числом відра, але цей кінцевий результат алгоритму хеша охоплює більший асортимент HashCode - це не повний алгоритм хешування, він достатньо малий, щоб його легко переробити. Таким чином, існує не поняття "безкоштовні відра", а "мінімальна кількість вільних відер", оскільки ви можете зберігати всі свої елементи в одному відрі. Швидше, це простір ключів вашого хеш-коду, який дорівнює ємності * (1 / load_factor). 40 елементів, коефіцієнт навантаження 0,25 = 160 відер.
користувач1122069

Я думаю, що час пошуку об’єкта з LinkedListпозначається як Amortized Constant Execution Timeі позначається +якO(1)+
Раф

19

З документації :

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

Це дійсно залежить від ваших конкретних вимог, немає "великого правила" для визначення початкового коефіцієнта навантаження.


Документація також говорить; "Зазвичай правило, коефіцієнт навантаження за замовчуванням (.75) пропонує хороший компроміс між витратами часу та простору." Тож для тих, хто не впевнений, типовим є хороше правило.
ferekdoley

4

Для HashMap DEFAULT_INITIAL_CAPACITY = 16 та DEFAULT_LOAD_FACTOR = 0.75f це означає, що MAX кількість ВСІХ записів у HashMap = 16 * 0.75 = 12 . Коли тринадцятому елементу буде додано місткість (розмір масиву) HashMap буде подвоєна! Ідеальна ілюстрація відповіла на це питання: введіть тут опис зображення зображення зроблено звідси:

https://javabypatel.blogspot.com/2015/10/what-is-load-factor-and-rehashing-in-hashmap.html


2

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

дуже довгий зв'язаний список.

І це начебто переможе питання.

Тож ось приклад, коли у мене чотири відра.

У мене в моєму HashSet поки що є слон і борсук.

Це досить гарна ситуація, правда?

Кожен елемент має нуль або один елемент.

Тепер ми помістили ще два елементи в наш HashSet.

     buckets      elements
      -------      -------
        0          elephant
        1          otter
         2          badger
         3           cat

Це теж не дуже погано.

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

Я дуже швидко можу подивитися на відро №1, і це не так

там і

Я знав, що його немає в нашій колекції.

Якщо я хочу знати, чи містить він кішку, я дивлюся на відро

номер 3,

Я знаходжу кота, я дуже швидко знаю, чи є він у нас

колекція.

Що робити, якщо я додаю коалу, ну це не так вже й погано.

             buckets      elements
      -------      -------
        0          elephant
        1          otter -> koala 
         2          badger
         3           cat

Можливо, тепер замість того, щоб у відрі №1 лише дивитись

один елемент,

Мені потрібно подивитися на два.

Але принаймні мені не треба дивитися на слона, борсука і

кіт.

Якщо я знову шукаю панду, вона може бути лише у відрі

№ 1 і

Мені не треба дивитись ні на що інше, ніж видру і

коала.

Але тепер я помістив алігатор у відро № 1 і ви можете

побачимо, можливо, куди це йде.

Що якщо відро № 1 продовжує збільшуватися і збільшуватися

Більше, тоді я в основному повинен переглядати все

ті елементи, які потрібно знайти

те, що повинно бути у відрі №1.

            buckets      elements
      -------      -------
        0          elephant
        1          otter -> koala ->alligator
         2          badger
         3           cat

Якщо я почну додавати рядки до інших відер,

правильно, проблема просто стає все більшою і більшою

одне відро.

Як ми не можемо наші відра занадто заповнитись?

Рішення тут таке

          "the HashSet can automatically

        resize the number of buckets."

Там HashSet розуміє, що відра отримують

занадто повний.

Це втрачає перевагу цього всього одного пошуку

елементів.

І це просто створить більше відра (як правило, вдвічі) та

потім помістіть елементи у правильне відро.

Отже, ось наша основна реалізація HashSet з окремими

прикування. Тепер я збираюся створити "саморозмінювану HashSet".

Цей HashSet зрозуміє, що це відра

занадто повно і

їй потрібно більше відра.

loadFactor - ще одне поле в нашому класі HashSet.

loadFactor являє собою середню кількість елементів на

відро,

над яким ми хочемо змінити розмір.

loadFactor - це баланс між простором і часом.

Якщо відра будуть занадто повними, ми змінимо розмір.

На це потрібен час, звичайно, але

це може заощадити нам час в дорозі, якщо відра є

трохи більше порожнього.

Подивимось приклад.

Ось HashSet, ми до цього часу додали чотири елементи.

Слон, собака, кішка і риба.

          buckets      elements
      -------      -------
        0          
        1          elephant
         2          cat ->dog
         3           fish
          4         
           5

У цей момент я вирішив, що loadFactor,

поріг,

середня кількість елементів на відро, що я в порядку

з, дорівнює 0,75.

Кількість відра - це відра. Довжина, яка дорівнює 6, і

на даний момент наш HashSet має чотири елементи, так що

поточний розмір - 4.

Ми змінимо розмір нашого HashSet, тобто додамо більше відра,

коли середня кількість елементів на відро перевищує

фактор навантаження

Це коли поточний розмір, поділений на buckets.length is

більше, ніж навантаженняFactor.

У цей момент середня кількість елементів на відро

це 4 ділиться на 6.

4 елементи, 6 відра, це 0,67.

Це менше порогу, який я встановив 0,75, тому ми

добре.

Нам не потрібно змінювати розмір.

Але тепер скажімо, ми додаємо дровочком.

                  buckets      elements
      -------      -------
        0          
        1          elephant
         2        woodchuck-> cat ->dog
         3           fish
          4         
           5

Вудхук опинився б у відрі №3.

У цей момент, currentSize дорівнює 5.

А тепер середня кількість елементів на відро

- поточний розмір, поділений на buckets.length.

Це 5 елементів, розділених на 6 відер, це 0,83.

І це перевищує коефіцієнт навантаження, який становив 0,75.

Щоб вирішити цю проблему, щоб зробити

відра, можливо, небагато

більш порожнім, щоб такі операції, як визначення того, чи є

відро містить

елемент буде трохи менш складним, я хочу змінити розмір

мій HashSet

Змінення розміру HashSet здійснює два кроки.

Спочатку я подвою кількість відра, у мене було 6 відра,

зараз у мене буде 12 відра.

Зауважимо, що loadFactor, який я встановив у 0,75, залишається таким же.

Але кількість відра, що змінилися, становить 12,

кількість елементів, що залишилися однаковими, дорівнює 5.

5 ділиться на 12 - це приблизно 0,42, це добре під нашим

loadFactor,

тож зараз у нас все гаразд.

Але ми не закінчили, тому що деякі з цих елементів є

неправильне відро зараз.

Наприклад, слон.

Слон був у відрі №2, оскільки кількість

символи в слоні

було 8.

У нас 6 відер, 8 мінус 6 - це 2.

Ось чому воно і закінчилося у №2.

Але тепер, коли у нас є 12 відра, 8 мод 12 - це 8, так

слон вже не належить до відра №2.

Слон належить до відра № 8.

А що з дрочком?

Вудчук став тим, хто розпочав всю цю проблему.

Вудхук опинився у відрі №3.

Тому що 9 мод 6 - це 3.

Але зараз ми робимо 9 мод 12.

9 mod 12 - 9, woodchuck переходить до відра № 9.

І ви бачите перевагу всього цього.

Зараз у відрі №3 є лише два елементи, тоді як до цього було 3.

Отже, ось наш код,

де у нас був наш HashSet з окремим ланцюжком

не робив жодних змін.

Тепер ось нова реалізація, де ми використовуємо розмір розміру.

Більшість цього коду однакові,

ми все ще будемо визначати, чи містить він

значення вже.

Якщо це не так, то ми визначимо, яке це відро

повинні зайти в і

потім додайте його у це відро, додайте до цього LinkedList.

Але тепер ми збільшуємо поле currentSize.

currentSize - це поле, яке відстежувало число

елементів у нашому HashSet.

Ми збільшимо його, а потім будемо шукати

при середньому навантаженні,

середня кількість елементів на відро.

Ми зробимо цей відділ тут.

Ми повинні зробити трохи кастинг тут, щоб переконатися

що ми отримуємо подвійний.

А потім ми порівняємо це середнє навантаження з полем

що я встановив як

0,75, коли я створив цей HashSet, наприклад, який був

фактор навантаження

Якщо середнє навантаження більше, ніж коефіцієнт навантаження,

це означає, що на відро занадто багато елементів

в середньому, і мені потрібно знову вставити.

Тож ось наша реалізація методу для повторного вставки

всі елементи.

По-перше, я створять локальну змінну під назвою oldBuckets.

Що стосується відра, як вони зараз стоять

перш ніж я почну все змінювати розмір.

Примітка. Я ще не створюю новий масив пов'язаних списків.

Я просто перейменувавши відра як старі Кошики.

Тепер пам’ятайте, відра були полем у нашому класі, я йду

тепер створити новий масив

пов'язаних списків, але в них буде вдвічі більше елементів

як це було вперше.

Тепер мені потрібно зробити перевстановлення,

Я збираюся переглядати всі старі відра.

Кожен елемент у OldBuckets є LinkedList рядків

це відро.

Я пройду це відро і отримаю кожен елемент у цьому

відро.

А тепер я знову вставлю його в нові Квіти.

Я отримаю його хеш-код.

Я розберуся, який це індекс.

І тепер я отримую нове відро, новий LinkedList від

струни і

Я додам його до того нового відра.

Таким чином, для резюме, HashSets, як ми бачили, є масивами Linked

Списки або відра.

HashSet, що змінює розмір, може реалізувати, використовуючи деяке співвідношення або


1

Я б вибрав таблицю розміром n * 1,5 або n + (n >> 1), це дало б коефіцієнт навантаження .66666 ~ без поділу, що повільно для більшості систем, особливо для портативних систем, де немає поділу в обладнання.

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