Як Java HashMap обробляє різні об'єкти з одним і тим же хеш-кодом?


223

Згідно з моїм розумінням, я думаю:

  1. Цілком законно, щоб два об'єкти мали один і той же хеш-код.
  2. Якщо два об'єкти рівні (використовуючи метод equals ()), то вони мають однаковий хеш-код.
  3. Якщо два об'єкти не рівні, вони не можуть мати однаковий хеш-код

Я прав?

Тепер, якщо я правильно, у мене є таке питання: HashMapВнутрішньо використовує хеш-код об'єкта. Отже, якщо два об'єкти можуть мати один і той же хеш-код, то як можна HashMapвідстежувати, який ключ він використовує?

Чи може хтось пояснити, як HashMapвнутрішньо використовує хеш-код об’єкта?


29
Для запису: №1 та №2 є правильними, №3 - неправильним: два об'єкти, які не рівні, можуть мати однаковий хеш-код.
Йоахім Зауер

6
№1 і №3 навіть суперечать один
одному

Дійсно, якщо №2 не дотримується, то реалізація рівняння () (або, можливо, хеш-код ()) невірна.
Йоахім Зауер

Відповіді:


346

Хешмап працює так (це трохи спрощено, але він ілюструє основний механізм):

У ньому є декілька "відер", які він використовує для зберігання пар ключових значень. Кожне відро має унікальне число - ось що ідентифікує це відро. Коли ви поміщаєте пару ключових значень на карту, хешмап перегляне хеш-код ключа та збереже пару у відрі, ідентифікатором якого є хеш-код ключа. Наприклад: хеш-код ключа 235 -> пара зберігається у відрі № 235. (Зверніть увагу, що одне відро може зберігати більше однієї пари ключ-значення).

Коли ви шукаєте значення в хешмапі, надаючи йому ключ, він спочатку перегляне хеш-код ключа, який ви дали. Потім хешмап загляне у відповідне відро, а потім порівняє ключ, який ви надали, з ключами всіх пар у відрі, порівнявши їх з equals().

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

Переглянувши вищевказаний механізм, ви також можете побачити, які вимоги необхідні щодо клавіш hashCode()та equals()методів:

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

  • Якщо два ключі різні, то не має значення, однакові чи їхні хеш-коди чи ні. Вони будуть зберігатися в одному відрі, якщо їхні хеш-коди однакові, і в цьому випадку хешмап буде використовувати їх equals()для розрізнення.


4
Ви писали: "і хешмап не зможе знайти пари ключових значень (тому що це буде шукати в тому ж відрі)". Чи можете ви пояснити, що будете шукати в одному відрі, скажімо, що ці два рівні об'єкти t1 і t2, і вони рівні, а t1 і t2 мають хеш-коди h1 і h2 відповідно. Отже, коли хешмап буде шукати t1, він виглядатиме у відрі h1, а t2 - у відрі t2?
Geek

19
Якщо два ключі рівні, але їх hashCode()метод повертає різні хеш-коди, то equals()і hashCode()методи класу клавіш порушують контракт, і ви отримаєте дивні результати при використанні цих ключів у HashMap.
Jesper

Кожне відро може мати кілька пар ключових значень, які використовують зв'язаний список всередині. Але моя плутанина - що тут відро? Яку структуру даних він використовує внутрішньо? Чи є зв’язок між відрами?
Ankit Sharma

1
@AnkitSharma Якщо ви хочете дійсно знати всі деталі, HashMapзнайдіть вихідний код , який ви можете знайти у файлі src.zipу вашому інсталяційному каталозі JDK.
Jesper

1
@ 1290 Єдине відношення між клавішами в одному відрі - це те, що вони мають однаковий хеш-код.
Джеспер

88

Ваше третє твердження неправильне.

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

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


6
як ти прийшов до 2 ^ 32 можливих об’єктів?
Geek

5
Я спізнююсь, але для тих, хто все ще цікавиться: хеш-код на Java - це int, а int має 2 ^ 32 можливих значення
Xerus

69

Діаграма структури HashMap

HashMap- це масив Entryоб'єктів.

Розглянемо HashMapяк лише масив об’єктів.

Погляньте, що це Object:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
 
}

Кожен Entryоб'єкт представляє пару ключ-значення. Поле nextпосилається на інший Entryоб'єкт, якщо відро має більше одного Entry.

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

Коли ви створюєте HashMapконструктор за замовчуванням

HashMap hashMap = new HashMap();

Масив створюється з розміром 16 і балансом завантаження за замовчуванням 0,75.

Додавання нової пари ключ-значення

  1. Обчисліть хеш-код для ключа
  2. Обчисліть позицію, hash % (arrayLength-1)де слід розмістити елемент (номер відра)
  3. Якщо ви спробуєте додати значення за допомогою ключа, який уже збережено HashMap, значення перезаписується.
  4. Інакше елемент додається у відро.

Якщо у відрі вже є щонайменше один елемент, додається новий і розміщується в першому положенні відра. Його nextполе відноситься до старого елементу.

Видалення

  1. Обчисліть хеш-код для даного ключа
  2. Обчисліть кількість відра hash % (arrayLength-1)
  3. Отримайте посилання на перший об'єкт Entry у відрі та методом рівних перебирайте всі записи в даному відрізку. Врешті-решт ми знайдемо правильне Entry. Якщо потрібний елемент не знайдений, повернітьсяnull

3
Це неправильно, hash % (arrayLength-1)було б hash % arrayLength. Але це насправді так hash & (arrayLength-1) . Тобто, оскільки він використовує потужність двох ( 2^n) для довжини масиву, беручи nнайменш значущі біти.
weston

Я думаю, що це не масив об'єктів Entity, а масив LinkedList / Tree. І кожне дерево внутрішньо має об'єкти Entity.
Mudit bhaintwal

@shevchyk чому ми зберігаємо ключ і хеш? які їх використання? Хіба ми тут не витрачаємо пам’ять?
roottraveller

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

2
@weston не тільки це, hashCode - це, intзвичайно, може бути негативним, якщо модуль на негативному номері дасть вам негативне число
Євген

35

Ви можете знайти відмінну інформацію на веб-сайті http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html

Узагальнити:

HashMap працює за принципом хешування

put (ключ, значення): HashMap зберігає як ключ, так і значення об'єкта як Map.Entry. Hashmap застосовує хеш-код (ключ), щоб отримати відро. якщо відбувається зіткнення, HashMap використовує LinkedList для зберігання об'єкта.

get (ключ): HashMap використовує хеш-код Key Object для пошуку місця розташування ковша, а потім викликає метод keys.equals () для виявлення правильного вузла в LinkedList та повернення пов'язаного об'єкта значення для цього ключа в Java HashMap.


3
Я знайшов відповідь, яку надав Джаспер, краще, я вважав, що блог більше спрямований на співбесіду, ніж на розуміння концепції
Narendra N

@NarendraN Я згоден з тобою.
Abhijit Gaikwad

22

Ось приблизний опис механізму HashMap'для Java 8версії (він може дещо відрізнятися від Java 6) .


Структури даних

  • Таблиця
    хеша Значення хеша обчислюється за допомогою hash()ключа, і він визначає, яке відро хештелю використовувати для даного ключа.
  • Список пов'язаних (поодинці)
    Коли кількість елементів у відрі невелика, використовується окремо пов'язаний список.
  • Червоно-чорне дерево
    Коли кількість елементів у відрі велика, використовується червоно-чорне дерево.

Заняття (внутрішні)

  • Map.Entry
    Представити єдину сутність на карті, сутність ключ / значення.
  • HashMap.Node
    Версія зв'язаного списку вузла.

    Він може представляти:

    • Хеш-відро.
      Тому що він має властивість хешу.
    • Вузол у спільно пов'язаному списку (таким чином, також глава пов'язаного списку ) .
  • HashMap.TreeNode
    Версія дерева.

Поля (внутрішні)

  • Node[] table
    Таблиця відра, (голова пов'язаних списків).
    Якщо відро не містить елементів, воно є нульовим, тому займайте лише місце відліку.
  • Set<Map.Entry> entrySet Набір сутностей.
  • int size
    Кількість організацій.
  • float loadFactor
    Укажіть, наскільки дозволена повна таблиця хешів, перш ніж змінювати розмір.
  • int threshold
    Наступний розмір, при якому потрібно змінити розмір.
    Формула:threshold = capacity * loadFactor

Методи (внутрішні)

  • int hash(key)
    Обчисліть хеш за ключем.
  • Як зіставити хеш на відро?
    Використовуйте таку логіку:

    static int hashToBucket(int tableSize, int hash) {
        return (tableSize - 1) & hash;
    }

Про потужність

У хеш-таблиці ємність означає кількість відра, з якого можна було отримати table.length.
Також можна обчислити через thresholdі loadFactor, таким чином, не потрібно визначати як поле класу.

Можна отримати ефективну потужність за допомогою: capacity()


Операції

  • Знайдіть об’єкт за ключем.
    Спочатку знайдіть відро за хеш-значенням, а потім зв’яжіть циклом список або шукайте відсортоване дерево.
  • Додайте сутність за допомогою ключа.
    Спочатку знайдіть відро відповідно до хеш-значення ключа.
    Потім спробуйте знайти значення:
    • Якщо знайдено, замініть значення.
    • В іншому випадку додайте новий вузол на початку зв'язаного списку або вставте в сортоване дерево.
  • Змінити розмір
    Коли буде thresholdдосягнуто, подвоїться ємність хештеля ( table.length), а потім виконати повторний хеш для всіх елементів для відновлення таблиці.
    Це може бути дорогою операцією.

Продуктивність

  • get & put
    Час складності полягає в O(1)тому, що:
    • Доступ до відра здійснюється через індекс масиву O(1).
    • Список у кожному відрізці має невелику довжину, таким чином, він може відображатися як O(1).
    • Розмір дерева також обмежений, тому що розширить ємність & переказ коли лічильник елемента збільшення, так міг би розглядати його як O(1)НЕ O(log N).

Надайте, будь ласка, приклад Як складність у часі O (1)
Jitendra

@jsroyal Це може пояснити складність більш чітко: en.wikipedia.org/wiki/Hash_table . Але коротше: пошук цільового відра - це O (1), тому що ви знайдете його за індексом у масиві; то всередині відра кількість елементів є невеликою і в середньому постійне число, незважаючи на загальну кількість елементів у всьому хештейлі, тому пошук цільового елемента всередині відра також є O (1); таким чином, O (1) + O (1) = O (1).
Ерік Ван

14

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

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

Більшість випадків добре написаний хеш-код не є ідеальним, але достатньо унікальний, щоб дати вам більш-менш постійний доступ.


11

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

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


6
import java.util.HashMap;

public class Students  {
    String name;
    int age;

    Students(String name, int age ){
        this.name = name;
        this.age=age;
    }

    @Override
    public int hashCode() {
        System.out.println("__hash__");
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("__eq__");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Students other = (Students) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    public static void main(String[] args) {

        Students S1 = new Students("taj",22);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Output:

__ hash __

116232

__ hash __

116201

__ hash __

__ hash __

2

Отже, ми бачимо, що якщо обидва об'єкти S1 і S2 мають різний зміст, ми майже впевнені, що наш перекритий метод Hashcode генерує різний Hashcode (116232,11601) для обох об'єктів. ЗАРАЗ, оскільки існують різні хеш-коди, тож навіть не завадить викликати метод EQUALS. Оскільки інший хеш-код ГАРАНТУЄ РІЗНИЙ вміст в об'єкті.

    public static void main(String[] args) {

        Students S1 = new Students("taj",21);
        Students S2 = new Students("taj",21);

        System.out.println(S1.hashCode());
        System.out.println(S2.hashCode());

        HashMap<Students,String > HM = new HashMap<Students,String > (); 
        HM.put(S1, "tajinder");
        HM.put(S2, "tajinder");
        System.out.println(HM.size());
    }
}

Now lets change out main method a little bit. Output after this change is 

__ hash __

116201

__ hash __

116201

__ hash __

__ hash __

__ eq __

1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally  calls Equal method to verify this. 


Conclusion 
If hashcode is different , equal method will not get called. 
if hashcode is same, equal method will get called.

Thanks , hope it helps. 

3

два об'єкти рівні, означає, що вони мають однаковий хеш-код, але не навпаки.

2 рівні об’єкти ------> у них однаковий хеш-код

2 об’єкти мають однаковий хеш-код ---- xxxxx -> вони НЕ рівні

Оновлення Java 8 в HashMap-

Ви робите цю операцію у своєму коді -

myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");

тому, припустимо, ваш хеш-код повернувся для обох ключів "old"і "very-old"той самий. Тоді що буде.

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

myHashmap.put("old","old-value");

тоді хеш-код для "old"обчислюється, і тому що хеш-код теж може бути дуже великим цілим числом, тому java внутрішньо зробив це - (хеш-код тут є хеш-кодом, а >>> - це правильний зсув)

hash XOR hash >>> 16

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

FYI - Запис - це клас в інтерфейсі Map - Map.Entry, з цим підписом / визначенням

class Entry{
          final Key k;
          value v;
          final int hash;
          Entry next;
}

тепер, коли ви виконуєте наступне твердження -

myHashmap.put("very-old","very-old-value");

і "very-old"дає такий самий хеш-код, як "old"і ця нова пара ключових значень знову надсилається до того ж індексу чи того ж відра. Але оскільки це відро не порожнє, то nextзмінна об'єкта Entry використовується для зберігання цієї нової пари ключових значень.

і це буде зберігатися як пов'язаний список для кожного об'єкта, який має однаковий хеш-код, але TRIEFY_THRESHOLD задається зі значенням 6. Отже, після досягнення цього, пов'язаний список перетворюється на збалансоване дерево (червоно-чорне дерево) з першим елементом як корінь.


приголомшлива відповідь (у)
Sudhanshu Gaur

2

Кожен об'єкт Entry представляє пару ключ-значення. Далі поле посилається на інший об'єкт Entry, якщо у відрі більше 1 запису.

Іноді може статися так, що хеш-коди для двох різних об'єктів однакові. У цьому випадку 2 об’єкти будуть збережені в одному відрі і будуть представлені як LinkedList. Точкою вступу є нещодавно доданий об’єкт. Цей об'єкт посилається на інший об'єкт із наступним полем і таким. Останній запис стосується нуля. Коли ви створюєте HashMap з конструктором за замовчуванням

Масив створюється з розміром 16 і балансом завантаження за замовчуванням 0,75.

введіть тут опис зображення

(Джерело)


1

Карта хешу працює за принципом хешування

Метод get (Key k) HashMap викликає метод hashCode на ключовий об'єкт і застосовує повернений hashValue до власної статичної хеш-функції, щоб знайти місце відра (резервний масив), де ключі та значення зберігаються у вигляді вкладеного класу під назвою Entry (Map). Вступ). Отже, ви зробили висновок, що з попереднього рядка, що і ключ, і значення зберігаються у відрі як форма об'єкта Entry. Отже, думка про те, що у відрі зберігається лише значення, не є правильним і не справить хорошого враження на інтерв'юера.

  • Щоразу, коли ми викликаємо метод get (Key k) на об'єкті HashMap. Спочатку він перевіряє, чи є ключ нульовим чи ні. Зауважте, що в HashMap може бути лише один нульовий ключ.

Якщо ключ є null, то Null-ключі завжди відображають хеш 0, таким чином, індекс 0.

Якщо ключ не є нульовим, він викличе хешфункцію на ключовому об'єкті, див. Рядок 4 вищевказаного методу, тобто key.hashCode (), тому після key.hashCode () повертає hashValue, рядок 4 виглядає як

            int hash = hash(hashValue)

і тепер, він застосовує повернений hashValue у свою функцію хешування.

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

Тепер остаточне значення хеш використовується для пошуку місця відра, в якому зберігається об'єкт Entry. Об'єкт введення зберігається у такому відрі (хеш, ключ, значення, bucketindex)


1

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

У нас є ключ, значення, HashCode та відро.

Колись ми будемо пов’язати кожну з них із наступним:

  • Відро -> Товариство
  • HashCode -> Адреса товариства (унікальна завжди)
  • Цінність -> Дім в суспільстві
  • Ключ -> Адреса будинку.

Використання Map.get (ключ):

Стіві хоче потрапити до будинку свого друга (Джоссі), який живе на віллі у VIP-товаристві, нехай це буде Товариство JavaLovers. Адреса Джоссе - це його SSN (який для всіх різний). Зберігається індекс, в якому ми дізнаємось назву Товариства на основі SSN. Цей індекс можна вважати алгоритмом для пошуку HashCode.

  • Назва товариства SSN
  • 92313 (Josse's) - JavaLovers
  • 13214 - AngularJSLovers
  • 98080 - JavaLovers
  • 53808 - Біологія

  1. Цей SSN (ключ) спочатку дає нам HashCode (з таблиці індексу), що є не що інше, як ім'я Товариства.
  2. Тепер багатомісні будинки можуть бути в одному суспільстві, тож HashCode може бути загальним.
  3. Припустимо, Товариство є загальним для двох будинків, як ми будемо визначати, до якого будинку ми збираємось, так, використовуючи клавішу (SSN), яка є не що інше, як адреса будинку

Використання Map.put (ключ, значення)

Це знаходить відповідне суспільство для цього Значення, знаходячи HashCode, а потім значення зберігається.

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


0

Це буде довга відповідь, випити напій і прочитати далі ...

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

Скажімо, я хочу зберігати 4 пари ключових значень -

{
girl => ahhan , 
misused => Manmohan Singh , 
horsemints => guess what”, 
no => way
}

Отже, для зберігання ключів нам потрібен масив із 4-х елементів. Тепер, як я зіставити одну з цих 4 клавіш на 4 індекси масиву (0,1,2,3)?

Тож java знаходить хеш-код окремих клавіш і відображає їх у конкретний індекс масиву. Формули хеш-коду -

1) reverse the string.

2) keep on multiplying ascii of each character with increasing power of 31 . then add the components .

3) So hashCode() of girl would be –(ascii values of  l,r,i,g are 108, 114, 105 and 103) . 

e.g. girl =  108 * 31^0  + 114 * 31^1  + 105 * 31^2 + 103 * 31^3  = 3173020

Хеш і дівчина !! Я знаю, що ти думаєш. Ваше захоплення цим диким дуетом може змусити вас пропустити важливу річ.

Чому java помножує його на 31?

Це тому, що 31 - непарний простір у формі 2 ^ 5 - 1. І непарний прайм зменшує шанс зіткнення Хеша

Тепер, як цей хеш-код відображається на індекс масиву?

Відповідь, Hash Code % (Array length -1) . Так “girl”відображено (3173020 % 3) = 1в нашому випадку. який є другим елементом масиву.

і значення "ахан" зберігається у LinkedList, пов'язаному з індексом масиву 1.

HashCollision - якщо ви спробуєте знайтиhasHCode ключі “misused”та “horsemints”скористатися описаними вище формулами, ви побачите, як обидві нам дають те саме 1069518484. Вуааа !! урок засвоєний -

2 рівних об'єкта повинні мати однаковий хеш-код, але немає гарантії, що хеш-код відповідає, то об'єкти рівні. Отже, він повинен зберігати обидва значення, що відповідають "неправильно використаним" та "кінним м'ятам" у відрі 1 (1069518484% 3).

Тепер хеш-карта виглядає так:

Array Index 0 
Array Index 1 - LinkedIst (“ahhan , Manmohan Singh , guess what”)
Array Index 2  LinkedList (“way”)
Array Index 3  

Тепер, якщо якесь тіло намагається знайти значення для ключа “horsemints” , java швидко знайде хеш-код його, модулює його та почне шукати його значення у відповідному LinkedList index 1. Таким чином, нам не потрібно шукати всі 4 індекси масиву, тим самим збільшуючи доступ до даних.

Але, зачекайте, одну секунду. У цьому пов'язаному списку відповідного масиву індексу 1 є 3 значення, як з’ясовується, яке було значенням для ключових «лошадей»?

Насправді я збрехав, коли сказав, що HashMap просто зберігає значення в LinkedList.

Він зберігає обидві пари ключових значень як запис на карті. Тож насправді карта виглядає приблизно так.

Array Index 0 
Array Index 1 - LinkedIst (<”girl => ahhan”> , <” misused => Manmohan Singh”> , <”horsemints => guess what”>)
Array Index 2  LinkedList (<”no => way”>)
Array Index 3  

Тепер ви можете побачити. Під час переходу через пов'язаний список, що відповідає ArrayIndex1, він фактично порівнює ключ кожного запису цього LinkedList з «кінчиками», а коли він знаходить, він просто повертає значення його.

Сподіваюся, вам було весело, читаючи його :)


Я думаю, що це неправильно: "Він зберігає ключі в масиві і значення в LinkedList."
ACV

кожен елемент у списку для кожного відра містить ключ та значення, а також посилання на наступний вузол.
ACV

0

Як сказано, картина коштує 1000 слів. Я кажу: якийсь код краще 1000 слів. Ось вихідний код HashMap. Отримати метод:

/**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

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

Давайте розглянемо put()метод:

  /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

Це трохи складніше, але стає зрозуміло, що новий елемент розміщується на вкладці у позиції, обчисленій на основі хеша:

i = (n - 1) & hashось iіндекс, куди буде поставлений новий елемент (або це "відро"). n- розмір tabмасиву (масив "відра").

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

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