Java предоставляет множество способов итерации по списку. Некоторые из них используют:
- API потока
- Интерфейс ListIterator
- Расширенный цикл for
- Простой цикл for
Мы не будем рассматривать основы каждого из вышеперечисленных способов, поскольку это выходит за рамки данной статьи, и большинство из нас и так хорошо о них осведомлены.
В этой статье мы сравним все методы циклирования с одним и тем же набором данных, чтобы сравнить их относительную производительность.
1. Различные методы обхода списка
Мы перечислим 4 различных способа, которые мне известны.
1.1. API потока
Java 8 Stream API предоставляет способы итерации по коллекции и работы с каждым элементом. Stream можно использовать как альтернативу for-loop.
private static List<Integer> list = new ArrayList<>();list.stream().forEach(consumerAction);
1.2. Улучшенный цикл for
В этой технике используется расширенный оператор for-each, представленный в Java 5.
private static List<Integer> list = new ArrayList<>();for(Integer i : list){// do other stuff}
1.3 Интерфейс ListIterator
private static List<Integer> list = new ArrayList<>();list.listIterator().forEachRemaining(consumerAction);
1.4 Простой цикл for
private static List<Integer> list = new ArrayList<>();int size = list.size();for(int j = 0; j < size ; j++){//do stuff}
2. Сравнение производительности
Мы создаем ArrayList и заполняем его одним миллионом экземпляров Integer. Затем мы пройдемся по списку, используя все вышеперечисленные способы. Таким образом мы сможем понять разницу в производительности.
2.1 Среда исполнения
- Ява 16
- Затмение 2021-06
2.2 Исходный код
пакет com.howtodoinjava.core.basic;импорт java.util.ArrayList;импорт java.util.List;импорт org.openjdk.jmh.annotations.Benchmark;импорт org.openjdk.jmh.annotations.BenchmarkMode;импорт org.openjdk.jmh.annotations.Fork;импорт org.openjdk.jmh.annotations.Mode;импортировать org.openjdk.jmh.infra.Blackhole;публичный класс ForLoopPerformanceTest{public static void main(String[] args) выдает исключение {org.openjdk.jmh.Main.main(аргументы);}частный статический список список = новый ArrayList();статический{для(int i=0; i черная дыра.потребление(i));}@Benchmark@Fork(значение = 1, разминка = 1)@BenchmarkMode(Режим.Пропускная способность)public void usingIterator(Черная дыра черная дыра) {список.listIterator().forEachRemaining(i -> blackhole.consume(i));}@Benchmark@Fork(значение = 1, разминка = 1)@BenchmarkMode(Режим.Пропускная способность)public void usingForEachLoop(Черная дыра черная дыра) {для(Целое число i : список){черная дыра.потребление(i);}}@Benchmark@Fork(значение = 1, разминка = 1)@BenchmarkMode(Режим.Пропускная способность)public void usingSimpleForLoop(Blackhole черная дыра) {для(int i = 0; i < list.size() ; i++){черная дыра.потребить(список.получить(i));}}}
При запуске вышеуказанного бенчмаркинга на основе JMH в консоли отображается следующий вывод:
Benchmark Mode Cnt Score Error UnitsForLoopPerformanceTest.usingForEachLoop thrpt 20 259.008 ± 17.888 ops/sForLoopPerformanceTest.usingIterator thrpt 20 256.016 ± 10.342 ops/sForLoopPerformanceTest.usingSimpleForLoop thrpt 20 495.308 ± 12.866 ops/sForLoopPerformanceTest.usingStream thrpt 20 257.174 ± 15.880 ops/s
Очевидно, что использование простого цикла for намного опережает в производительности. Остальные три способа обеспечивают схожие показатели производительности.
3. Заключение
Хотя простой цикл for обеспечивает наилучшую производительность, другие методы циклов обеспечивают гораздо лучшую читаемость.
Кроме того, мы используем цикл с более чем миллионом элементов в списке, что не является практичным сценарием в большинстве приложений.
Поэтому, если в списке нет миллионов элементов, используйте новые функции Java, такие как Stream API или улучшенные циклы for.