В этом руководстве по Java рассматриваются различные способы итерации по HashMap и сравнивается производительность каждого метода, что позволяет принять обоснованное решение.
Рекомендуется изучить, как HashMap работает внутри, чтобы знать, как данные хранятся в HashMap. Если мой последний похожий пост, мы сравнивали различные «циклы for», доступные в Java. Эти исследования обычно помогают в настройке лучших практик для вашего следующего проекта.
1. Введение
В этом посте я решил сравнить производительность различных способов обхода HashMap в Java. HashMap — очень широко используемый класс, и большую часть времени мы извлекаем значение с помощью метода get(Object key), предоставляемого классом. Но иногда требуется перебрать всю Map и извлечь все пары ключ-значение, хранящиеся в ней.
Например, анализ всех параметров запроса, отправленных клиентом. В таких случаях для каждого клиента мы итерируем всю карту как минимум один раз в процессе обработки запроса.
Если мы используем этот тип итерации во многих местах кода и есть много запросов, то мы, конечно, хотели бы оптимизировать код итерации HashMap, чтобы использовать его наилучшим образом. Мой нижеприведенный анализ поможет нам определиться с нашим следующим шагом.
2. Различные способы итерации по карте
Давайте начнем с различных способов итерации по HashMap. HashMap был определен следующим образом:
Map<String, Integer> map = new HashMap();
2.1.HashMap.forEach()
Начиная с Java 8, мы можем использовать метод forEach() для перебора ключей и значений, хранящихся в Map.
map.forEach((key, value) -> {System.out.println(key + ": " + value);//...});
2.2. Итерация по записям карты
Метод entrySet() возвращает все записи в представлении Set, которые мы можем перебирать аналогично обычному HashSet в Java.
for(Map.Entry<String, Integer> entry : map.entrySet()) {String key = entry.getKey();Integer value = entry.getValue();//...}
2.3. Итерация по ключам карты и доступ к значениям
Метод keySet() возвращает все ключи как Set. Мы можем перебрать все ключи и использовать их для доступа к значениям из Map. Обратите внимание, что для доступа к значению требуется дополнительный шаг, поэтому в большинстве случаев это не рекомендуемый подход.
for(String key : map.keySet()) {Integer value = map.get(key);//...}
2.4 Использование итератора
Поскольку мы получаем представление Set из entrySet() и keySet(), мы можем использовать Iterator для итерации по Map.
Следующий код выполняет итерацию по записям карты с помощью итератора.
Iterator<Entry<String, Integer>> entryIterator = map.entrySet().iterator();while(entryIterator.hasNext()) {Map.Entry<String, Integer> entry = entryIterator.next();String key = entry.getKey();Integer value = entry.getValue();//...}
Аналогично, следующий код выполняет итерацию по ключам карты с помощью итератора и получает доступ к значениям с помощью ключей.
Iterator<String> keySetIterator = map.keySet().iterator();while(keySetIterator.hasNext()) {String key = keySetIterator.next();Integer value = map.get(key);//...}
3. Сравнение эффективности различных методов
Теперь давайте сравним производительность всех типов итераций для общего набора данных, хранящегося в Map. Мы храним 1 миллион пар ключ-значение в Map.
Карта карта = new HashMap();для(int i = 0; i < 10_00_000; i++) {map.put(String.valueOf(i), i);}
Мы выполним итерацию по карте всеми четырьмя способами. Мы также извлечем ключ и значение из карты для всех записей наиболее подходящим способом. Мы используем модуль JMH для сравнительного анализа производительности всех методов.
@Benchmarkpublic void usingForEach(Blackhole blackhole) {map.forEach((key, value) -> {blackhole.consume(key);blackhole.consume(value);});}@Benchmarkpublic void usingEntrySetWithForLoop(Blackhole blackhole) {for(Map.Entry<String, Integer> entry : map.entrySet()) {blackhole.consume(entry.getKey());blackhole.consume(entry.getValue());}}@Benchmarkpublic void usingKeySetWithForLoop(Blackhole blackhole) {for(String key : map.keySet()) {blackhole.consume(map.get(key));}}@Benchmarkpublic void usingEntrySetWithForIterator(Blackhole blackhole) {Iterator<Entry<String, Integer>> entryIterator = map.entrySet().iterator();while(entryIterator.hasNext()) {Map.Entry<String, Integer> entry = entryIterator.next();blackhole.consume(entry.getKey());blackhole.consume(entry.getValue());}}
4. Результат
Сравнительный балл вышеуказанной программы:
c.h.c.collections.map...usingForEach thrpt 15 89.044 ± 2.703 ops/sc.h.c.collections.map...usingEntrySetWithForLoop thrpt 15 54.906 ± 6.326 ops/sc.h.c.collections.map...usingKeySetWithForLoop thrpt 15 52.163 ± 4.517 ops/sc.h.c.collections.map...usingEntrySetWithForIterator thrpt 15 63.494 ± 4.334 ops/s
Хотя между всеми методами mobe нет большой разницы, мы можем видеть, что:
- При использовании цикла for с методами keySet() и entrySet() производительность практически одинакова.
- Использование методов iterator() работает сравнительно хуже.
- Удивительно, но метод forEach() занимает больше всего времени. Это потому, что метод forEach использует внутреннюю итерацию, что означает, что логика итерации обрабатывается внутри реализации HashMap. Он вызывает предоставленное лямбда-выражение для каждого элемента в HashMap. Эта внутренняя итерация сопровождается некоторыми накладными расходами из-за выполнения лямбда-выражения и дополнительных вызовов функций.
Ради чистоты кода можно заключить, что:
- Если Map содержит только несколько записей, то использование HashMap.forEach() — лучший способ перебрать HashMap.
- Если Map содержит только миллион записей, то использование HashMap.entrySet() — лучший способ перебрать HashMap.
5. Заключение
10 лаков — это очень большое число для большинства требований приложения. Хотя разница не очень существенна в миллисекундах, по сравнению с тем, что было очень большим в случае циклов for. Я считаю, что большинство из нас могут жить с такой незначительной разницей.
Но если вы хотите сделать вывод, использование набора записей очень конкретно является более мощным и дает лучшую производительность, чем другие. Результат варьируется от 20% до 50%, когда указанная выше программа выполняется несколько раз.
Пожалуйста, дайте мне знать, что вы думаете по поводу вышеприведенного анализа.