В этой статье показаны три алгоритма поиска повторяющихся элементов в потоке.
Установить.добавить()
Установить.добавить()
Установить.добавить()
В конце статьи мы используем тест JMH, чтобы проверить, какой из них является самым быстрым алгоритмом.
1. Фильтр и набор.добавить()
Set.add()
возвращает значение false, если элемент уже был в наборе; давайте посмотрим на контрольный показатель в конце статьи.
package com.mkyong; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class JavaDuplicated1 { public static void main(String[] args) { // 3, 4, 9 Listlist = Arrays.asList(5, 3, 4, 1, 3, 7, 2, 9, 9, 4); Set result = findDuplicateBySetAdd(list); result.forEach(System.out::println); } public static Set findDuplicateBySetAdd(List list) { Set items = new HashSet<>(); return list.stream() .filter(n -> !items.add(n)) // Set.add() returns false if the element was already in the set. .collect(Collectors.toSet()); } }
Выход
3 4 9
2. Карта и коллекционеры. Группировка по
2.1 Создайте Карту
с помощью Коллекционеров.группировАв
и найдите элементы, которые насчитывают > 1.
package com.mkyong; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; public class JavaDuplicated2 { public static void main(String[] args) { // 3, 4, 9 Listlist = Arrays.asList(5, 3, 4, 1, 3, 7, 2, 9, 9, 4); Set result = findDuplicateByGrouping(list); result.forEach(System.out::println); } public static Set findDuplicateByGrouping(List list) { return list.stream() .collect(Collectors.groupingBy(Function.identity() , Collectors.counting())) // create a map {1=1, 2=1, 3=2, 4=2, 5=1, 7=1, 9=2} .entrySet().stream() // Map -> Stream .filter(m -> m.getValue() > 1) // if map value > 1, duplicate element .map(Map.Entry::getKey) .collect(Collectors.toSet()); } }
Выход
3 4 9
3. Выход
Он сравнивает каждый элемент со списком – Коллекции.частота (список, i)
.
package com.mkyong; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class JavaDuplicated3 { public static void main(String[] args) { // 3, 4, 9 Listlist = Arrays.asList(5, 3, 4, 1, 3, 7, 2, 9, 9, 4); Set result = findDuplicateByFrequency(list); result.forEach(System.out::println); } public static Set findDuplicateByFrequency(List list) { return list.stream().filter(i -> Collections.frequency(list, i) > 1) .collect(Collectors.toSet()); } }
Выход
3 4 9
4. Эталонный показатель JMH
Простой тест JMH для трех вышеперечисленных алгоритмов позволяет найти дублированные элементы из потока размером 1000.
org.openjdk.jmh jmh-core 1.23 org.openjdk.jmh jmh-generator-annprocess 1.23
package com.mkyong; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @State(Scope.Benchmark) @Fork(value = 2, jvmArgs = {"-Xms4G", "-Xmx4G"}) public class BenchmarkFindDuplicate { private ListDATA_FOR_TESTING; @Setup public void init() { // random 1000 size DATA_FOR_TESTING = new Random().ints(1000, 1, 1000) .boxed() .collect(Collectors.toList()); } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(BenchmarkFindDuplicate.class.getSimpleName()) .forks(1) .build(); new Runner(opt).run(); } @Benchmark public void setAdd(Blackhole bh) { Set items = new HashSet<>(); Set collect = DATA_FOR_TESTING.stream() .filter(n -> !items.add(n)) .collect(Collectors.toSet()); bh.consume(collect); } @Benchmark public void groupingBy(Blackhole bh) { Set collect = DATA_FOR_TESTING.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) .entrySet() .stream() .filter(m -> m.getValue() > 1) .map(Map.Entry::getKey) .collect(Collectors.toSet()); bh.consume(collect); } @Benchmark public void frequency(Blackhole bh) { Set collect = DATA_FOR_TESTING.stream() .filter(i -> Collections.frequency(DATA_FOR_TESTING, i) > 1) .collect(Collectors.toSet()); bh.consume(collect); } }
Выход
# JMH version: 1.23 # VM version: JDK 11.0.6, OpenJDK 64-Bit Server VM, 11.0.6+10 # VM invoker: /usr/lib/jvm/adoptopenjdk-11-hotspot-amd64/bin/java # VM options: -Xms4G -Xmx4G # Warmup: 5 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Average time, time/op # Benchmark: com.mkyong.BenchmarkFindDuplicate.frequency # Run progress: 0.00% complete, ETA 00:05:00 # Fork: 1 of 1 # Warmup Iteration 1: 0.827 ms/op # Warmup Iteration 2: 0.821 ms/op # Warmup Iteration 3: 0.812 ms/op # Warmup Iteration 4: 0.822 ms/op # Warmup Iteration 5: 0.822 ms/op Iteration 1: 0.814 ms/op Iteration 2: 0.810 ms/op Iteration 3: 0.779 ms/op Iteration 4: 0.776 ms/op Iteration 5: 0.814 ms/op Result "com.mkyong.BenchmarkFindDuplicate.frequency": 0.799 ±(99.9%) 0.075 ms/op [Average] (min, avg, max) = (0.776, 0.799, 0.814), stdev = 0.019 CI (99.9%): [0.724, 0.874] (assumes normal distribution) # JMH version: 1.23 # VM version: JDK 11.0.6, OpenJDK 64-Bit Server VM, 11.0.6+10 # VM invoker: /usr/lib/jvm/adoptopenjdk-11-hotspot-amd64/bin/java # VM options: -Xms4G -Xmx4G # Warmup: 5 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Average time, time/op # Benchmark: com.mkyong.BenchmarkFindDuplicate.groupingBy # Run progress: 33.33% complete, ETA 00:03:20 # Fork: 1 of 1 # Warmup Iteration 1: 0.040 ms/op # Warmup Iteration 2: 0.038 ms/op # Warmup Iteration 3: 0.037 ms/op # Warmup Iteration 4: 0.036 ms/op # Warmup Iteration 5: 0.039 ms/op Iteration 1: 0.039 ms/op Iteration 2: 0.039 ms/op Iteration 3: 0.039 ms/op Iteration 4: 0.039 ms/op Iteration 5: 0.039 ms/op Result "com.mkyong.BenchmarkFindDuplicate.groupingBy": 0.039 ±(99.9%) 0.001 ms/op [Average] (min, avg, max) = (0.039, 0.039, 0.039), stdev = 0.001 CI (99.9%): [0.038, 0.040] (assumes normal distribution) # JMH version: 1.23 # VM version: JDK 11.0.6, OpenJDK 64-Bit Server VM, 11.0.6+10 # VM invoker: /usr/lib/jvm/adoptopenjdk-11-hotspot-amd64/bin/java # VM options: -Xms4G -Xmx4G # Warmup: 5 iterations, 10 s each # Measurement: 5 iterations, 10 s each # Timeout: 10 min per iteration # Threads: 1 thread, will synchronize iterations # Benchmark mode: Average time, time/op # Benchmark: com.mkyong.BenchmarkFindDuplicate.setAdd # Run progress: 66.67% complete, ETA 00:01:40 # Fork: 1 of 1 # Warmup Iteration 1: 0.027 ms/op # Warmup Iteration 2: 0.028 ms/op # Warmup Iteration 3: 0.026 ms/op # Warmup Iteration 4: 0.026 ms/op # Warmup Iteration 5: 0.027 ms/op Iteration 1: 0.026 ms/op Iteration 2: 0.027 ms/op Iteration 3: 0.028 ms/op Iteration 4: 0.028 ms/op Iteration 5: 0.028 ms/op Result "com.mkyong.BenchmarkFindDuplicate.setAdd": 0.027 ±(99.9%) 0.003 ms/op [Average] (min, avg, max) = (0.026, 0.027, 0.028), stdev = 0.001 CI (99.9%): [0.024, 0.031] (assumes normal distribution) # Run complete. Total time: 00:05:01 REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell. Benchmark Mode Cnt Score Error Units BenchmarkFindDuplicate.frequency avgt 5 0.799 ± 0.075 ms/op BenchmarkFindDuplicate.groupingBy avgt 5 0.039 ± 0.001 ms/op BenchmarkFindDuplicate.setAdd avgt 5 0.027 ± 0.003 ms/op Process finished with exit code 0
В потоке Java 8 отфильтруйте с помощью Set. Add()
– это самый быстрый алгоритм поиска повторяющихся элементов, потому что он выполняет цикл только один раз.
Setitems = new HashSet<>(); return list.stream() .filter(n -> !items.add(n)) .collect(Collectors.toSet());
Коллекции .частота
самая медленная, потому что она сравнивает каждый элемент со списком – Коллекции.частота (список, i)
. Если мы увеличим размер списка, производительность снизится.
return list.stream().filter(i -> Collections.frequency(list, i) > 1) .collect(Collectors.toSet());
Рекомендации
- Учебник по тестированию Java JMH
- Как подсчитать дублированные элементы в списке Java
- Поток JavaDoc
Оригинал: “https://mkyong.com/java8/java-8-find-duplicate-elements-in-a-stream/”