Главная » Хабрахабр » Внутренняя работа HashMap в Java

Внутренняя работа HashMap в Java

[примечание от автора перевода] Перевод был выполнен для собственных нужд, но если кому -то это окажется полезным, значит мир стал хоть немного, но лучше!

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

Как и в предыдущей статье, HashMap содержит массив Node и Node может представлять класс, содержащий следующие объекты:

  1. int — хэш
  2. K — ключ
  3. V — значение
  4. Node — следующий элемент

Для начала мы рассмотрим процесс хеширования. Теперь мы увидим, как все это работает.

Хэширование

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

Мой класс Key: Здесь я использую свой собственный класс Key и таким образом могу переопределить метод hashCode() для демонстрации различных сценариев.

// специальный класс Key для переопределени методов hashCode()
// и equals()
class Key
@Override public int hashCode() { return (int)key.charAt(0); } @Override public boolean equals(Object obj) { return key.equals((String)obj); }
}

Таким образом, если первые символы строки одинаковые, то и хэш коды будут одинаковыми. Здесь переопределенный метод hashCode() возвращает ASCII код первого символа строки. Не стоит использовать подобную логику в своих программах.

Поскольку HashCode допускает ключ типа null, хэш код null всегда будет равен 0. Этот код создан исключительно для демонстрации.

Метод hashCode()

Метод hashCode() класса Object возвращает ссылку памяти объекта в целочисленной форме (идентификационный хеш (identity hash code)). Метод hashCode() используется для получения хэш кода объекта. Это говорит о том, что метод реализован как нативный, поскольку в java нет какого -то метода позволяющего получить ссылку на объект. Сигнатура метода public native hashCode(). В классе HashMap метод hashCode() используется для вычисления корзины (bucket) и следовательно вычисления индекса. Допускается определять собственную реализацию метода hashCode().

Метод equals()

Метод реализованн в классе Object. Метод equals используется для проверки двух объектов на равенство. В классе HashMap метод equals() используется для проверки равенства ключей. Вы можете переопределить его в своем собственном классе. В случае, если ключи равны, метод equals() возвращает true, иначе false.

Корзины (Buckets)

Он используется для хранения узлов (Nodes). Bucket -это единственный элемент массива HashMap. В этом случае для связи узлов используется структура данных связанный список. Два или более узла могут иметь один и тот -же bucket. Отношение между bucket и capacity выглядит следующим образом: Bucket -ы различаются по ёмкости (свойство capacity).

capacity = number of buckets * load factor

Чем лучше реализованн ваш метод hashCode(), тем лучше будут использоваться ваши bucket -ы. Один bucket может иметь более, чем один узел, это зависит от реализации метода hashCode().

Вычисление индекса в HashMap

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

index = hashCode(key) & (n-1).

В нашем примере я рассматриваю n, как значение по умолчанию равное 16. где n равна числу bucket или значению длины массива.

  • изначально пустой hashMap: здесь размер hashmap равен 16:

HashMap map = new HashMap();

HashMap:

  • вставка пар Ключ — Значение: добавить одну пару ключ — значение в конец HashMap

map.put(new Key("vishal"), 20);

Шаги:

  1. Оно будет сгенерированно, как 118. Вычислить значение ключа {"vishal"}.

  2. Вычислить индекс с помощью метода index, который будет равен 6.

  3. Создать объект node.

    {
    int hash = 118 // {"vishal"} не строка, а
    // объект класса Key
    Key key = {"vishal"} Integer value = 20
    Node next = null
    }

  4. Поместить объект в позицию с индексом 6, если место свободно.

Теперь HashMap выглядит примерно так:

  • добавление другой пары ключ — значение: теперь добавим другую пару

map.put(new Key("sachin"), 30);

Шаги:

  1. Оно будет сгенерированно, как 115. Вычислить значение ключа {"sachin"}.

  2. Вычислить индекс с помощью метода index, который будет равен 3.

  3. Создать объект node.

    {
    int hash = 115
    Key key = {"sachin"}
    Integer value = 30
    Node next = null
    }

  4. Поместить объект в позицию с индексом 3, если место свободно.

Теперь HashMap выглядит примерно так:

  • в случае возникновения коллизий: теперь добавим другую пару

map.put(new Key("vaibhav"), 40);

Шаги:

  1. Оно будет сгенерированно, как 118. Вычислить значение ключа {"vaibhav"}.

  2. Вычислить индекс с помощью метода index, который будет равен 6.

  3. Создать объект node.

    {
    int hash = 118
    Key key = {"vaibhav"}
    Integer value = 20
    Node next = null
    }

  4. Поместить объект в позицию с индексом 6, если место свободно.

  5. В данном случае в позиции с индексом 6 уже существует другой объект, этот случай называется коллизией.

  6. В таком случае проверям с помощью методов hashCode() и equals(), что оба ключа одинаковы.

  7. Если ключи одинаковы, заменить текущее значение новым.

  8. Иначе связать новый и старый объекты с помощью структуры данных "связанный список", указав ссылку на следующий объект в текущем и сохранить оба под индексом 6.

Теперь HashMap выглядит примерно так:

Метод get(K key) используется для получения значения по ключу. Теперь давайте попробуем метод get(), для получения значения. Если ключ не известен, то получить значение невозможно.

  • получаем значение по ключу sachin:

map.get(new Key("sachin"));

Шаги:

  1. Он был сгенерирован, как 115. Вычислить хэш код объекта {“sachin”}.

  2. Вычислить индекс с помощью метода index, который будет равен 3.

  3. Если они равны -вернуть значение, иначе выполнить проверку для следующего элемента, если он существует. Перейти по индексу 3 и сравнить ключ первого элемента с имеющемся значением.

  4. В нашем случае элемент найден и возвращаемое значение равно 30.

  • получаем значение по ключу vaibahv:

map.get(new Key("vaibhav"));

Шаги:

  1. Он был сгенерирован, как 118. Вычислить хэш код объекта {"vaibhav"}.

  2. Вычислить индекс с помощью метода index, который будет равен 6.

  3. Если они равны -вернуть значение, иначе выполнить проверку для следующего элемента, если он существует. Перейти по индексу 6 и сравнить ключ первого элемента с имеющемся значением.

  4. В данном случае он не найден и следующий объект node не равен null.

  5. Если следующий объект node равен null, возвращаем null.

  6. Если следующий объект node не равен null, переходим к нему и повторяем первые три шага до тех пор, пока элемент не будет найден или следующий объект node не будет равен null.

// Java программа для иллюстрации
// внутренней работы HashMap
import java.util.HashMap; class Key { String key; Key(String key) { this.key = key; } @Override public int hashCode() { int hash = (int)key.charAt(0); System.out.println("hashCode for key: " + key + " = " + hash); return hash; } @Override public boolean equals(Object obj) { return key.equals(((Key)obj).key); }
} // Driver class
public class GFG { public static void main(String[] args) { HashMap map = new HashMap(); map.put(new Key("vishal"), 20); map.put(new Key("sachin"), 30); map.put(new Key("vaibhav"), 40); System.out.println(); System.out.println("Value for key sachin: " + map.get(new Key("sachin"))); System.out.println("Value for key vaibhav: " + map.get(new Key("vaibhav"))); }
}

Вывод:

hashCode for key: vishal = 118
hashCode for key: sachin = 115
hashCode for key: vaibhav = 118 hashCode for key: sachin = 115
Value for key sachin: 30
hashCode for key: vaibhav = 118
Value for key vaibhav: 40

Изменения в Java 8

Это сравнения для поиска верного ключа в связанном списке -линейная операция и в худшем случае сложность равнa O(n). Как мы уже значем в случае возникновения коллизий объект node сохраняется в структуре данных "связанный список" и метод equals() используется для сравнения ключей.

Это означает, что HashMap в начале сохраняет объекты в связанном списке, но после того, как колличество элементов в хэше достигает определенного порога происходит переход к сбалансированным деревьям. Для исправления этой проблемы в Java 8 после достижения определенного порога вместо связанных списков используются сбалансированные деревья. Что улучшает производительность в худшем случае с O(n) до O(log n).

Важный момент

  1. Сложность операций get() и put() практически константна до тех пор, пока не будет проведенно повторное хэширование.
  2. В случае коллизий, если индексы двух и более объектов node одинаковые, объекты node соединяются с помощью связанного списка, т.е. ссылка на второй объект node хранится в первом, на третий во втором и т.д.
  3. Если данный ключ уже существует в HashMap, значение перезаписывается.
  4. Хэш код null равен 0.
  5. Когда объект получается по ключу происходят переходы по связанному списку до тех пор, пока объект не будет найден или ссылка на следующий объект не будет равна null.

Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Не позволяйте 3D-принтеру лениться

Наверное, у каждого из нас есть гаджет или устройство, которые мы не используем: тренажёр, плеер, кухонный инвентарь, всякие странные штуки для автомобиля, шнуры, хабы и проч. Лежат они, новенькие, с гарантийниками и годами ждут своего часа, которому чаще всего уже ...

[Перевод] Знакомимся с альфа-версией снапшотов томов в Kubernetes

перев.: оригинальная статья была недавно опубликована в блоге Kubernetes и написана сотрудниками компаний Google и Huawei (Jing Xu, Xing Yang, Saad Ali), активную деятельность которых вы непременно видели в GitHub'е проекта, если когда-либо интересовались фичами и проблемами K8s, связанными с ...