С выпуском JDK 1.8 Streams API у HashMap появилось больше методов обхода, но какой метод обхода выбрать? Вместо этого это стало проблемой.
эта статьяНачнем с метода обхода HashMap, а затем проанализируем преимущества и недостатки различных методов обхода HashMap с точки зрения производительности, принципа действия и безопасности., основное содержание этой статьи показано на следующем рисунке:
Обход HashMap
HashMap Траверсы можно разделить на следующие 4 категории по общему направлению:
- Итератор (Iterator) способ обхода;
- Для каждого пути прохождения;
- обход лямбда-выражений (JDK 1.8+);
- Обход API потоков (JDK 1.8+).
Однако каждый тип имеет разные методы реализации, поэтому конкретные методы обхода можно разделить на следующие 7 типов:
- Обход с помощью итератора (Iterator) EntrySet;
- Обход с помощью итератора (Iterator) KeySet;
- Используйте For Each EntrySet для обхода;
- Используйте For Each KeySet для обхода;
- Обход с помощью лямбда-выражений;
- Используйте Streams API для однопоточного обхода;
- Обход выполняется в многопоточном режиме с использованием Streams API.
Далее давайте посмотрим на конкретный код реализации каждого метода обхода.
1. Итератор EntrySet
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Java中文社群");
// 遍历
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
}
Результат выполнения вышеуказанной программы:
1
Java
2
JDK
3
Spring Framework
4
MyBatis framework
5
Сообщество китайского языка Java
2. Набор ключей итератора
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Java中文社群");
// 遍历
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.println(key);
System.out.println(map.get(key));
}
}
}
Результат выполнения вышеуказанной программы:
1
Java
2
JDK
3
Spring Framework
4
MyBatis framework
5
Сообщество китайского языка Java
3.ForEach EntrySet
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Java中文社群");
// 遍历
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
}
Результат выполнения вышеуказанной программы:
1
Java
2
JDK
3
Spring Framework
4
MyBatis framework
5
Сообщество китайского языка Java
4.ForEach KeySet
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Java中文社群");
// 遍历
for (Integer key : map.keySet()) {
System.out.println(key);
System.out.println(map.get(key));
}
}
}
Результат выполнения вышеуказанной программы:
1
Java
2
JDK
3
Spring Framework
4
MyBatis framework
5
Сообщество китайского языка Java
5.Lambda
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Java中文社群");
// 遍历
map.forEach((key, value) -> {
System.out.println(key);
System.out.println(value);
});
}
}
Результат выполнения вышеуказанной программы:
1
Java
2
JDK
3
Spring Framework
4
MyBatis framework
5
Сообщество китайского языка Java
6. Однопоточность Streams API
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Java中文社群");
// 遍历
map.entrySet().stream().forEach((entry) -> {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
});
}
}
Результат выполнения вышеуказанной программы:
1
Java
2
JDK
3
Spring Framework
4
MyBatis framework
5
Сообщество китайского языка Java
7. Многопоточность Streams API
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Java中文社群");
// 遍历
map.entrySet().parallelStream().forEach((entry) -> {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
});
}
}
Результат выполнения вышеуказанной программы:
4
MyBatis framework
5
Сообщество китайского языка Java
1
Java
2
JDK
3
Spring Framework
Тестирование производительности
Затем мы используем официальный инструмент тестирования производительности Oracle JMH (Java Microbenchmark Harness, JAVA Microbenchmark Test Suite) для проверки производительности этих семи циклов.
Прежде всего, нам нужно представить структуру JMH, вpom.xml
Добавьте в файл следующую конфигурацию:
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.23</version>
<scope>provided</scope>
</dependency>
Затем напишите тестовый код следующим образом:
@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 1s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class HashMapCycleTest {
static Map<Integer, String> map = new HashMap() {{
// 添加数据
for (int i = 0; i < 100; i++) {
put(i, "val:" + i);
}
}};
public static void main(String[] args) throws RunnerException {
// 启动基准测试
Options opt = new OptionsBuilder()
.include(HashMapCycle.class.getSimpleName()) // 要导入的测试类
.output("/Users/admin/Desktop/jmh-map.log") // 输出测试结果的文件
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void entrySet() {
// 遍历
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
Integer k = entry.getKey();
String v = entry.getValue();
}
}
@Benchmark
public void forEachEntrySet() {
// 遍历
for (Map.Entry<Integer, String> entry : map.entrySet()) {
Integer k = entry.getKey();
String v = entry.getValue();
}
}
@Benchmark
public void keySet() {
// 遍历
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer k = iterator.next();
String v = map.get(k);
}
}
@Benchmark
public void forEachKeySet() {
// 遍历
for (Integer key : map.keySet()) {
Integer k = key;
String v = map.get(k);
}
}
@Benchmark
public void lambda() {
// 遍历
map.forEach((key, value) -> {
Integer k = key;
String v = map.get(k);
});
}
@Benchmark
public void streamApi() {
// 单线程遍历
map.entrySet().stream().forEach((entry) -> {
Integer k = entry.getKey();
String v = entry.getValue();
});
}
public void parallelStreamApi() {
// 多线程遍历
map.entrySet().parallelStream().forEach((entry) -> {
Integer k = entry.getKey();
String v = entry.getValue();
});
}
}
все добавлено@Benchmark
Аннотированные методы будут тестироваться, так как производительность parallelStream должна быть лучшей для многопоточной версии, поэтому он не будет участвовать в тесте.Результаты тестирования остальных шести методов следующие:
где столбец Score представляет среднее время выполнения,±
Символ указывает на ошибку. Из вышеприведенных результатов видно, чтоlambda
выражение и дваentrySet
аналогичен по производительности и работает быстрее всего, за ним следуетstream
, то дваkeySet
.
Примечание. Приведенные выше результаты основаны на тестовой среде: JDK 1.8 / Mac mini (2018 г.) / Idea 2020.1.
в заключении
С точки зрения производительности мы должны попытаться использоватьlambda
илиentrySet
пересекатьMap
собирать.
Анализ байт-кода
Чтобы понять приведенные выше результаты теста, нам нужно передать весь код обхода черезjavac
Скомпилируйте в байт-код, чтобы увидеть конкретную причину.
После компиляции мы используем Idea для открытия байт-кода со следующим содержимым:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.example;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class HashMapTest {
static Map<Integer, String> map = new HashMap() {
{
for(int var1 = 0; var1 < 2; ++var1) {
this.put(var1, "val:" + var1);
}
}
};
public HashMapTest() {
}
public static void main(String[] var0) {
entrySet();
keySet();
forEachEntrySet();
forEachKeySet();
lambda();
streamApi();
parallelStreamApi();
}
public static void entrySet() {
Iterator var0 = map.entrySet().iterator();
while(var0.hasNext()) {
Entry var1 = (Entry)var0.next();
System.out.println(var1.getKey());
System.out.println((String)var1.getValue());
}
}
public static void keySet() {
Iterator var0 = map.keySet().iterator();
while(var0.hasNext()) {
Integer var1 = (Integer)var0.next();
System.out.println(var1);
System.out.println((String)map.get(var1));
}
}
public static void forEachEntrySet() {
Iterator var0 = map.entrySet().iterator();
while(var0.hasNext()) {
Entry var1 = (Entry)var0.next();
System.out.println(var1.getKey());
System.out.println((String)var1.getValue());
}
}
public static void forEachKeySet() {
Iterator var0 = map.keySet().iterator();
while(var0.hasNext()) {
Integer var1 = (Integer)var0.next();
System.out.println(var1);
System.out.println((String)map.get(var1));
}
}
public static void lambda() {
map.forEach((var0, var1) -> {
System.out.println(var0);
System.out.println(var1);
});
}
public static void streamApi() {
map.entrySet().stream().forEach((var0) -> {
System.out.println(var0.getKey());
System.out.println((String)var0.getValue());
});
}
public static void parallelStreamApi() {
map.entrySet().parallelStream().forEach((var0) -> {
System.out.println(var0.getKey());
System.out.println((String)var0.getValue());
});
}
}
Как видно из результатов, помимо Lambda и Streams API, цикл по итератору иfor
циклический обходEntrySet
Окончательный сгенерированный код одинаков, все они создают объект обхода в цикле.Entry
,Следующее:
public static void entrySet() {
Iterator var0 = map.entrySet().iterator();
while(var0.hasNext()) {
Entry var1 = (Entry)var0.next();
System.out.println(var1.getKey());
System.out.println((String)var1.getValue());
}
}
public static void forEachEntrySet() {
Iterator var0 = map.entrySet().iterator();
while(var0.hasNext()) {
Entry var1 = (Entry)var0.next();
System.out.println(var1.getKey());
System.out.println((String)var1.getValue());
}
}
при передаче итераторов иfor
циклически проходимыйKeySet
Код также тот же, а именно:
public static void keySet() {
Iterator var0 = map.keySet().iterator();
while(var0.hasNext()) {
Integer var1 = (Integer)var0.next();
System.out.println(var1);
System.out.println((String)map.get(var1));
}
}
public static void forEachKeySet() {
Iterator var0 = map.keySet().iterator();
while(var0.hasNext()) {
Integer var1 = (Integer)var0.next();
System.out.println(var1);
System.out.println((String)map.get(var1));
}
}
Итак, мы используем итераторы илиfor
циклEntrySet
, их производительность одинакова, потому что байт-коды, которые они в конечном итоге генерируют, в основном одинаковы;KeySet
Оба метода обхода также схожи.
анализ производительности
EntrySet
почему, чемKeySet
Высокая производительность обусловлена тем, чтоKeySet
используется во время циклаmap.get(key)
,иmap.get(key)
Это эквивалентно повторному обходуMap
коллекция для запросаkey
соответствующее значение. Зачем использовать слово "опять"? Это потому, что при использовании итераторов илиfor
При зацикливании он фактически был пройден один разMap
коллекция, так что используйте сноваmap.get(key)
При запросе это эквивалентно обходу дважды.
иEntrySet
пройдено всего один разMap
коллекции, а затем передайте код "Entrykey
иvalue
значения вставляютсяEntry
объект, так что получитьkey
иvalue
Вам не нужно просматривать коллекцию Map, когда вы получаете значение, вам просто нужно получить значение из объекта Entry.
так,EntrySet
коэффициент производительностиKeySet
производительность удваивается, потому чтоKeySet
Эквивалентно двойному циклуMap
коллекция, иEntrySet
только цикл один раз.
тестирование безопасности
Исходя из приведенных выше результатов теста производительности и принципиального анализа, я думаю, каждый должен выбрать метод обхода, который уже известен, а затем мы начнем с точки зрения «безопасности» анализировать, какой метод обхода безопаснее.
Мы разделяем вышеуказанный обход на четыре категории для тестирования: метод итератора, метод цикла For, метод Lambda и метод Stream, Код теста выглядит следующим образом.
1. Метод итератора
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
if (entry.getKey() == 1) {
// 删除
System.out.println("del:" + entry.getKey());
iterator.remove();
} else {
System.out.println("show:" + entry.getKey());
}
}
Результат выполнения вышеуказанной программы:
show:0
del:1
show:2
Результаты теста:Цикл итератора удаляет безопасность данных.
2. Метод цикла
for (Map.Entry<Integer, String> entry : map.entrySet()) {
if (entry.getKey() == 1) {
// 删除
System.out.println("del:" + entry.getKey());
map.remove(entry.getKey());
} else {
System.out.println("show:" + entry.getKey());
}
}
Результат выполнения вышеуказанной программы:
Результаты теста:Удаление данных в цикле For небезопасно.
3. Лямбда-метод
map.forEach((key, value) -> {
if (key == 1) {
System.out.println("del:" + key);
map.remove(key);
} else {
System.out.println("show:" + key);
}
});
Результат выполнения вышеуказанной программы:
Результаты теста:Удаление данных в лямбда-цикле небезопасно.Правильный способ удаления Lambda:
// 根据 map 中的 key 去判断删除
map.keySet().removeIf(key -> key == 1);
map.forEach((key, value) -> {
System.out.println("show:" + key);
});
Результат выполнения вышеуказанной программы:
show:0
show:2
Как видно из приведенного выше кода, вы можете сначала использоватьLambda
изremoveIf
Удаление лишних данных, а затем зацикливание — правильный способ манипулирования коллекциями.
4. Поточный метод
map.entrySet().stream().forEach((entry) -> {
if (entry.getKey() == 1) {
System.out.println("del:" + entry.getKey());
map.remove(entry.getKey());
} else {
System.out.println("show:" + entry.getKey());
}
});
Результат выполнения вышеуказанной программы:
Результаты теста:Небезопасно удалять данные в цикле Stream.
Правильный способ зацикливания потока:
map.entrySet().stream().filter(m -> 1 != m.getKey()).forEach((entry) -> {
if (entry.getKey() == 1) {
System.out.println("del:" + entry.getKey());
} else {
System.out.println("show:" + entry.getKey());
}
});
Результат выполнения вышеуказанной программы:
show:0
show:2
Как видно из приведенного выше кода, вы можете использоватьStream
серединаfilter
Фильтрация бесполезных данных и последующий обход также являются безопасным способом манипулирования коллекциями.
в заключении
Мы не можем использовать коллекции в обходеmap.remove()
Чтобы удалить данные, это небезопасный режим работы, но мы можем использовать итераторiterator.remove()
Способ удаления данных, который является безопасным способом удаления коллекции. Точно так же мы также можем использовать лямбда вremoveIf
удалить данные заранее или использоватьfilter
Безопасно отфильтровывать удаляемые данные для зацикливания.Конечно, мы также можемfor
Удаление данных перед циклом также является потокобезопасным во время обхода.
Суммировать
В этой статье мы рассказали о 4 методах обхода HashMap: iterator, for, lambda, stream и 7 конкретных методах обхода, С точки зрения комплексной производительности и безопасности,мы должны попытаться использоватьentrySet
для обхода HashMap, что является безопасным и эффективным.
последние слова
Оригинальность – это непросто, если вы считаете, что эта статья вам полезна, нажмите на значок "отличный", это самая большая поддержка и поощрение для автора, спасибо.
Ссылки и благодарности
Woohoo. Java guides.net/2020/03/5 - нет...
Подпишитесь на официальный аккаунт «Java Chinese Community» и ответьте на «Галантные товары», чтобы получить 50 оригинальных галантерейных товаров.Топ-лист.