Код
#статьи

HashMap в Java: что это такое и зачем нужно

Это как словарь, только лучше. Рассказываем и показываем, как работать с «мэпами» в «кофейном» языке.

Иллюстрация: Оля Ежак для Skillbox Media

Хранить данные в Java можно по-разному. Кто-то записывает их в массив, кто-то создаёт сотни переменных… А кто-то складывает всё в хешмэпы, чтобы потом в доли секунды проводить любые операции с данными.

Сегодня мы узнаем, что такое HashMap, в чём его фишка, как его создать и какие методы для работы с ним есть в Java. В конце статьи есть шпаргалка, которую можно сохранить и держать под рукой во время занятий.

Из этой статьи вы узнаете:


Что такое HashMap в Java

HashMap, или хеш-таблица, — это одна из структур данных в Java, и она чем-то похожа на обычный толковый словарь. Здесь у каждого слова есть своё определение, а количество этих слов ограничено только «лексиконом» говорящего. Однако в программировании используются немного другие обозначения: слова называются ключами, а определения — значениями.

Ключ — это как закладка в книге, по которой легко найти нужную страницу. А значение — это содержимое страницы книги.

В этом примере ключи — это краткое описание происходящего, а значения — номера страниц из книги Фёдора Михайловича Достоевского «Преступление и наказание»
Инфографика: Майя Мальгина для Skillbox Media

Ключами могут быть не только строки, а вообще любые данные: числа, объекты, нумерация, булевы переменные. Главное — чтобы они были уникальными. А для значений вообще нет никаких ограничений: они даже могут повторяться для разных ключей.

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

Смотрите:

  • В массиве все элементы хранятся последовательно, а значит, чтобы найти нужный, компьютеру приходится сначала перебрать все предыдущие. Если массив большой — процесс займёт много времени.
  • В хеш-таблице у нас есть указатель на конкретное место в памяти — ключ. По нему можно сразу «перепрыгнуть» к нужным данным и использовать их для своих целей.

Хеш-таблица так называется, потому что у каждого элемента в ней есть хеш. Хеш — это числовой номер, который показывает, в какое место таблицы нужно добавить новый элемент. Для вычисления хеша используют разные хеш-функции: можно использовать стандартные, например, метод hashCode(), а можно разработать свою.

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

Сначала ключи словаря преобразуются в числа с помощью хеш-функции. Получатся два новых числа, которые и будут индексами ячеек памяти, куда запишутся данные
Инфографика: Майя Мальгина для Skillbox Media

Может возникнуть вопрос: а что если у двух элементов получится одинаковый хеш? Например, если следовать принципу из картинки выше, хеш для чисел 674 и 690, будет одинаковым — 2. В какую ячейку тогда запишутся данные?

Отвечаем: на этот случай в HashMap есть система антиколлизий. Коллизия — это как раз тот случай, когда два элемента хотят записаться в одно и то же место. Чтобы избежать коллизий, используют такую схему:

  • если место в памяти свободно — записать данные;
  • если место занято — запомнить, что здесь есть данные, и сделать в этом месте цепочку данных.

Цепочка данных — это связный список. Старые данные находятся в начале списка, а новые прикрепляются к его хвосту. Получается такая связка:

Проблема: пытаемся записать данные в ячейку памяти, а там они уже есть. Решение: сделать цепочку данных
Инфографика: Майя Мальгина для Skillbox Media

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

Ещё одна особенность хеш-таблиц в том, что, когда мы создаём HashMap, он по умолчанию выделяет место под 16 ячеек. Неважно, заполним мы их сразу или позже. Главное — чтобы было место под новые элементы.

Как только количество элементов достигнет 75% от количества ячеек, HashMap увеличит свой размер в два раза. То есть максимальный размер станет уже 32. При этом изначальный размер HashMap можно задавать вручную, как и коэффициент загрузки, — то есть степень заполнения, при которой хеш-таблица будет создавать новые ячейки.

Давайте теперь познакомимся с основными методами для работы с HashMap. Узнаем, как создавать хеш-таблицы, менять их, удалять элементы и другое.

Как создать HashMap в Java

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

Чтобы создать HashMap, сначала нужно импортировать библиотеку:

import java.util.HashMap;

Теперь создаём HashMap как обычный класс:

HashMap numbersAndNames = new HashMap();

Но этого недостаточно, так как Java — строго типизированный язык. Это значит, что нам нужно явно указать типы переменных: и для ключей, и для значений. Делается это при помощи треугольных скобок:

HashMap<Integer, String> numbersAndNames = new HashMap<>();

Обратите внимание, что мы также добавили пару треугольных скобок при создании класса. Скобки справа — это как бы ещё одно указание, какие типы будут у ключей и значений, а возьмёт Java эти типы из левой части выражения.

Теперь давайте создадим пару элементов внутри словаря.

Как добавить элементы в HashMap

Чтобы добавить элемент в HashMap, используют метод put:

HashMap<Integer, String> numbersAndNames = new HashMap<>();

numbersAndNames.put(1234567, "Андрей");

System.out.println(numbersAndNames);

Результат:

{1234567=Андрей}

Метод put принимает два аргумента — сначала ключ, а потом значение. В нашем случае ключом был импровизированный номер телефона 1234567, а значением — строка с именем Андрей.

Давайте добавим ещё пару элементов:

HashMap<Integer, String> numbersAndNames = new HashMap<>();

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");
numbersAndNames.put(1266667, "Катя");

System.out.println(numbersAndNames);

Результат:

{1234567=Андрей, 1266667=Катя, 1333567=Денис}

Видим, что новые элементы добавились в словарь, но в странном порядке. У нас поменялись местами номера Дениса и Кати, хотя мы явно указали, в каком порядке они должны идти. Дело в том, что в HashMap используют хеши, чтобы находить места для записи данных, о чём мы рассказывали чуть выше.

Давайте попробуем добавить новый элемент с уже существующим номером и новым именем. Посмотрим, что получится:

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");
numbersAndNames.put(1333567, "Маша");

System.out.println(numbersAndNames);

Результат:

{1234567=Андрей, 1333567=Маша}

Видим, что второй элемент перезаписал первый. И теперь уже никак не узнать, что до это номер принадлежал Денису — теперь по нему можно дозвониться только до Маши. Поэтому нужно быть аккуратными, когда добавляете новый элемент по уже существующему ключу, — есть риск потерять данные.

Чтобы проверить, есть ли какие-то данные по ключу, применяют метод containsKey:

System.out.println(numbersAndNames.containsKey(1333567));

Результат:

true

Можем сделать простую проверку, перед тем как добавлять новые элементы с помощью условного оператора if: если такого ключа не существует, то спокойно добавляем:

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");
// Условная конструкция, которая проверяет наличие ключа        
if (!numbersAndNames.containsKey(1333567)) {
    numbersAndNames.put(1333567, "Маша");
}

Теперь перезаписать существующий ключ уже не получится.

Ещё один способ проверить, существует ли ключ, — через его значение. Для этого используют метод containsValue:

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");

System.out.println(numbersAndNames.containsValue("Андрей"));

Результат:

true

Переходим к следующей важной теме — попробуем получить и удалить пару элементов из нашей мапы.

Как получить и удалить элементы из HashMap

Чтобы удалить элемент, зная его ключ, используют метод remove:

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");
       
numbersAndNames.remove(1333567);


System.out.println(numbersAndNames);

Результат:

{1234567=Андрей}

Чтобы удалить сразу все элементы, используют функцию clear. Применяйте с осторожностью:

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");
       
numbersAndNames.clear();

System.out.println(numbersAndNames);

Результат:

{}

А чтобы получить значение по ключу, нужно использовать метод get:

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");

String denis = numbersAndNames.get(1333567);

System.out.println(denis);

Результат:

Денис

В HashMap у элементов нет индексов, как у массивов. Поэтому обращение к элементам происходит через ключи и, в некоторых случаях, через значения.

Ещё мы можем получить сразу все ключи или значения из HashMap. Для этого используют две функции: keySet — для ключей и values — для значений.

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");

System.out.println(numbersAndNames.keySet());

Результат:

[1234567, 1333567]

Теперь то же самое, но со значениями:

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");

System.out.println(numbersAndNames.values());

Результат:

[Андрей, Денис]

Видим, что ключи и значения вернулись в виде массива. Теперь к ним уже можно обращаться по индексам.

А для того чтобы получить все пары «ключ —значение», существует метод entrySet:

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");

System.out.println(numbersAndNames.entrySet());

Результат:

[1234567=Андрей, 1333567=Денис]

Таким способом можно превратить словарь в массив, который использует индексы. Например, это полезно, когда нужно последовательно перебирать все значения в словаре и как-то изменять их. Скажем, можем взять и убрать у всех имён лишние пробелы или заменить буквы «е» на «ё».

Как узнать размер HashMap

Если мы хотим узнать, сколько всего элементов находится в HashMap, можно использовать метод size:

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");

System.out.println(numbersAndNames.size());

Результат:

2

Проверить, не пустой ли HashMap, можно с помощью метода isEmpty:

numbersAndNames.put(1234567, "Андрей");
numbersAndNames.put(1333567, "Денис");

System.out.println(numbersAndNames.isEmpty());

Результат:

false

Как менять размер HashMap: методы resize и transfer

Выше мы рассказывали, что HashMap по умолчанию выделяет место для 16 элементов. А когда их количество достигает предельного коэффициента загрузки (стандартный — 75%), язык удвоит размер и позволяет вместить ещё 16 элементов.

Но, оказывается, этим процессом можно управлять и самостоятельно. Для этого существуют два метода — resize и transfer. Первый позволяет изменить размер HashMap, а второй — переместить элементы из одного словаря в другой. Сейчас разберёмся подробнее.

Внутрь метода resize передаётся новый размер в виде целого числа:

numbersAndNames.resize(40)

После выполнения этой команды, максимальный размер словаря станет 40 элементов. Как только их количество достигнет предела, он удвоится.

Метод transfer нужен, чтобы перенести все элементы из одного словаря в другой — обычно большего размера. При этом происходит перераспределение этих элементов: если в старом HashMap у элементов были длинные хвосты из данных, то в новом их быть не должно.

Например, давайте создадим новый словарь на 100 элементов, а затем переместим данные в него из другого.

HashMap<Integer, String> resizedNumbersAndNames = new resizedNumbersAndNames<>(100);

numbersAndNames.transfer(resizedNumbersAndNames);

System.out.println(resizedNumbersAndNames);

{1234567=Андрей, 1266667=Катя, 1333567=Денис}

Готово! Элементы переместились в новый словарь большего размера.

Шпаргалка: как работать с HashMap

Давайте разберём основные методы для работы с хеш-таблицами.

МетодЧто делает
import java.util.HashMap;Импортирует библиотеку для работы со словарями
HashMap<Type1, Type2> myHashMap = new HashMap<>();Создаёт новый словарь, где Type1 и Type2 — это типы данных ключей и значений
myHashMap.put(key, value);Добавляет элемент в словарь с ключом key и значением value
myHashMap.containsKey(key)Проверяет, есть ли в словаре элемент с ключом key
myHashMap.remove(key);Удаляет элемент с ключом key
myHashMap.clear();Полностью очищает словарь
myHashMap.get(key);Возвращает элемент с ключом key
myHashMap.keySet()Возвращает список всех ключей из словаря
myHashMap.values()Возвращает список всех значений из словаря
myHashMap.entrySet()Возвращает список пар «ключ — значение»
myHashMap.size()Возвращает размер словаря
myHashMap.isEmpty()Проверяет, пуст ли словарь
myHashMap.resize(newSize)Изменяет максимальный размер словаря на значение newSize
myHashMap.transfer(resizedHashMap)Перемещает и перераспределяет элементы в новый словарь resizedHashMap

Что запомнить

Давайте кратко подытожим, что мы узнали о хеш-таблицах:

  • HashMap, или хеш-таблица, — структура данных, которая хранит элементы в формате «ключ — значение». Ключ — это уникальный номер элемента, а значение — содержимое элемента.
  • Хеш-таблицы намного эффективнее обычных массивов при получении данных. Поэтому их уместно использовать, когда к данным приходится часто обращаться.
  • Для каждого ключа HashMap создаёт свой хеш. Это такой числовой идентификатор, которые указывает элементу, в какую ячейку памяти записаться. Чтобы вычислять хеши, используют хеш-функции.
  • В хеш-таблицах могут возникнуть коллизии — это когда хеш у двух элементов совпадает. В таком случае новый элемент добавляется в хвост старого, в результате в одной ячейке появляется целая цепочка данных. А заведует этим процессом система антиколлизий.
  • Чтобы управлять элементами в HashMap используют разные методы — например, put, get, remove и size.

Больше интересного про код — в нашем телеграм-канале. Подписывайтесь!

Изучайте IT на практике — бесплатно

Курсы за 2990 0 р.

Я не знаю, с чего начать
Научитесь: Профессия Java-разработчик Узнать больше
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована