Java 8 потоков-сбор против уменьшения
когда вы используете collect() vs reduce()? У кого-нибудь есть хорошие, конкретные примеры того, когда определенно лучше идти в ту или иную сторону?
Javadoc упоминает, что collect () является изменяемым сокращением.
учитывая, что это изменчивое сокращение, я предполагаю, что оно требует синхронизации (внутренне), что, в свою очередь, может нанести ущерб производительности. Предположительно reduce() более легко распараллеливается за счет необходимости создания новых данных структура для возвращения после каждого шага в уменьшении.
приведенные выше утверждения являются догадками, однако, и я хотел бы, чтобы эксперт перезвонил здесь.
7 ответов:
во-первых, возвращаемые значения разные:
<R,A> R collect(Collector<? super T,A,R> collector) T reduce(T identity, BinaryOperator<T> accumulator)так
collectвозвращаетR, тогда какreduceвозвращаетT- типаStream.
reduceэто "раза" операция, она применяет двоичный оператор к каждому элементу в потоке, где первый аргумент оператора является возвращаемым значением предыдущего приложения, а второй аргумент является текущим элементом потока.
collectionагрегация операция, в которой создается "коллекция" и каждый элемент "добавляется" в эту коллекцию. Коллекции в разных частях потока суммируются.The документ, который вы связали дает причину для наличия двух разных подходов:
если бы мы хотели взять поток строк и объединить их в a одиночная длинная строка, мы смогли достигнуть этого с обычным уменьшением:
String concatenated = strings.reduce("", String::concat)мы бы получили желаемый результат, и он даже будет работать параллельно. Тем не менее, мы не можем быть довольны выступлением! Такое реализация будет делать много копирования строк, а запуск время будет O (n^2) в количестве символов. Более производительный подход будет заключаться в накоплении результатов в StringBuilder, который является изменяемым контейнером для накопления строк. Мы можем использовать тот же метод для распараллеливания изменчивой редукции, что и с обычной сокращение.
Итак, дело в том, что распараллеливание одинаково в обоих случаях, но в
reducecase мы применяем функцию к самим элементам потока. Вcollectcase мы применяем функцию к изменяемому контейнеру.
причина просто в том, что:
collect()может работать только С mutable объекты результата.reduce()- это предназначен для работы С неизменяемые объекты результата."
reduce()С неизменным" примерpublic class Employee { private Integer salary; public Employee(String aSalary){ this.salary = new Integer(aSalary); } public Integer getSalary(){ return this.salary; } } @Test public void testReduceWithImmutable(){ List<Employee> list = new LinkedList<>(); list.add(new Employee("1")); list.add(new Employee("2")); list.add(new Employee("3")); Integer sum = list .stream() .map(Employee::getSalary) .reduce(0, (Integer a, Integer b) -> Integer.sum(a, b)); assertEquals(new Integer(6), sum); }"
collect()С примером изменяемых"например, если вы хотите вручную вычислить сумму с помощью
collect()он не может работать сBigDecimalтолько сMutableIntСorg.apache.commons.lang.mutableнапример. Смотрите:public class Employee { private MutableInt salary; public Employee(String aSalary){ this.salary = new MutableInt(aSalary); } public MutableInt getSalary(){ return this.salary; } } @Test public void testCollectWithMutable(){ List<Employee> list = new LinkedList<>(); list.add(new Employee("1")); list.add(new Employee("2")); MutableInt sum = list.stream().collect( MutableInt::new, (MutableInt container, Employee employee) -> container.add(employee.getSalary().intValue()) , MutableInt::add); assertEquals(new MutableInt(3), sum); }это работает, потому что аккумулятор
container.add(employee.getSalary().intValue());не должен возвращать новый объект с результатом, но, чтобы изменить состояние изменчивоеcontainerтипаMutableInt.если вы хотите использовать
BigDecimalвместоcontainerвы не могли бы использоватьcollect()методcontainer.add(employee.getSalary());не менятьcontainer, потому чтоBigDecimalона неизменна. (Помимо этогоBigDecimal::newне будет работать какBigDecimalне имеет пустого конструктора)
нормальное сокращение предназначено для объединения двух неизменяемые значения, такие как int, double и т. д. и произвести новый; это неизменяемые
пусть поток будет a
в сокращении,
У вас будет ((A # b) # c) # d
где # это интересная операция, которую вы хотели бы сделать.
в сборе,
У вашего коллекционера будет какая-то коллекционная структура K.
K потребляет a. Затем K потребляет b. Затем K потребляет c. Затем K потребляет d.
в конце вы спрашиваете K, каков конечный результат.
K, тогда дает его тебе.
они очень отличается потенциальным объемом памяти во время выполнения. В то время как
collect()собирает и выкладывает все данные в коллекцию,reduce()явно просит вас указать, как уменьшить данные, которые сделали это через поток.например, если вы хотите прочитать некоторые данные из файла, обработать их и поместить в какую-либо базу данных, вы можете получить код потока java, подобный этому:
streamDataFromFile(file) .map(data -> processData(data)) .map(result -> database.save(result)) .collect(Collectors.toList());в этом дело, мы используем
collect()чтобы заставить java передавать данные через и заставить его сохранить результат в базе данных. Безcollect()данные никогда не читал и никогда не хранятся.этот код счастливо генерирует
java.lang.OutOfMemoryError: Java heap spaceошибка выполнения, если размер файла достаточно велик или размер кучи достаточно низок. Очевидная причина заключается в том, что он пытается сложить все данные, которые прошли через поток (и, по сути, уже были сохранены в базе данных) в результирующую коллекцию, и это взрывается куча.однако, если вы замените
collect()Сreduce()-- это больше не будет проблемой, так как последний уменьшит и отбросит все данные, которые сделали это.в представленном примере просто замените
collect()С чем-то сreduce:.reduce(0L, (aLong, result) -> aLong, (aLong1, aLong2) -> aLong1);вам не нужно даже заботиться, чтобы сделать расчет зависит от
resultпоскольку Java не является чистым языком FP (функционального программирования) и не может оптимизировать данные, которые не являются используется на дне ручья из-за возможных побочных эффектов.
вот пример кода
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7); int sum = list.stream().reduce((x,y) -> { System.out.println(String.format("x=%d,y=%d",x,y)); return (x + y); }).get();система.из.println (sum);
вот результат выполнения:
x=1,y=2 x=3,y=3 x=6,y=4 x=10,y=5 x=15,y=6 x=21,y=7 28уменьшить функцию обрабатывать два параметра, первый параметр-это предыдущее возвращаемое значение int поток, второй параметр-текущий вычислите значение в потоке, оно суммирует первое значение и текущее значение как первое значение в следующей caculation.
По данным документы
сборники reducing () наиболее полезны при использовании в многоуровневом сокращении, после groupingBy или partitioningBy. Чтобы выполнить простое сокращение потока, используйте Stream.вместо этого уменьшите (BinaryOperator).
Так что в основном вы бы использовали
reducing()только при принудительном сборе. Вот еще один пример:For example, given a stream of Person, to calculate the longest last name of residents in each city: Comparator<String> byLength = Comparator.comparing(String::length); Map<String, String> longestLastNameByCity = personList.stream().collect(groupingBy(Person::getCity, reducing("", Person::getLastName, BinaryOperator.maxBy(byLength))));По данным в этом уроке сокращение иногда менее эффективно
операция уменьшения всегда возвращает новое значение. Однако функция аккумулятора также возвращает новое значение каждый раз, когда она обрабатывает элемент потока. Предположим, что вы хотите свести элементы потока к более сложному объекту, такому как коллекция. Это может привести к снижению производительности приложения. Если ваша операция сокращения включает добавление элементов в коллекцию, то каждый раз, когда ваша функция аккумулятора обрабатывает элемент, он создает новую коллекцию, которая включает в себя элемент, который является неэффективным. Вместо этого было бы более эффективно обновить существующую коллекцию. Вы можете сделать это с потоком.соберите метод, который описывается в следующем разделе...
таким образом, идентичность "повторно используется" в сценарии сокращения, поэтому немного эффективнее идти с
.reduceесли это возможно.
Comments