Порядок сбора потоков / сумматора - PullRequest
0 голосов
/ 05 сентября 2018

Это в основном продолжение моего ответа .

Предположим, что я работаю над настраиваемым сборщиком и предположим, что accumulator всегда добавит некоторый элемент в коллекцию, возвращаемую поставщиком, есть ли вероятность, что при вызове combiner один из промежуточных результатов будет пусто? Пример, вероятно, намного проще для понимания.

Предположим, у меня есть List чисел, и я хочу разделить его в списке списков, где 2 - это разделитель. Так, например, у меня есть 1, 2, 3, 4, 2, 8, результат должен быть [[1], [3, 4], [8]]. Этого не очень сложно достичь (не судите слишком много кода, я написал что-то быстрое, просто чтобы я мог написать этот вопрос).

List<List<Integer>> result = Stream.of(1, 2, 3, 4, 2, 8)
            .collect(Collector.of(
                    () -> new ArrayList<>(),
                    (list, elem) -> {
                        if (list.isEmpty()) {
                            List<Integer> inner = new ArrayList<>();
                            inner.add(elem);
                            list.add(inner);
                        } else {
                            if (elem == 2) {
                                list.add(new ArrayList<>());
                            } else {
                                List<Integer> last = list.get(list.size() - 1);
                                last.add(elem);
                            }
                        }
                    },
                    (left, right) -> {
                        // This is the real question here:
                        // can left or right be empty here?
                        return left;
                    }));

Это, вероятно, не имеет значения в этом примере, но вопрос в следующем: могут ли элементы в combiner быть пустыми List? Я действительно очень склонен сказать NO, так как в документации они упоминаются как:

combiner - ассоциативная, не мешающая работе функция без сохранения состояния, которая принимает два контейнера частичных результатов и объединяет их.

Хорошо, что частичное для меня является признаком того, что accumulator был вызван на них, прежде чем они достигли combiner, но просто хотел быть уверенным.

1 Ответ

0 голосов
/ 05 сентября 2018

Нет гарантии, что аккумулятор был применен к контейнеру перед слиянием. Другими словами, списки для объединения могут быть пустыми.

Чтобы продемонстрировать это:

IntStream.range(0, 10).parallel().boxed()
         .filter(i -> i >= 3 && i < 7)
         .collect(ArrayList::new, List::add, (l1,l2)->{
             System.out.println(l1.size()+" + "+l2.size());
             l1.addAll(l2);
         });

На моей машине он печатает:

0 + 0
0 + 0
0 + 0
1 + 1
0 + 2
0 + 2
1 + 1
2 + 0
2 + 2

Разделение рабочей нагрузки происходит в списке источников, когда результат операции фильтра еще не известен. Каждый чанк обрабатывается одинаково, без проверки того, поступил ли какой-либо элемент в аккумулятор.

Помните, что начиная с Java 9, вы также можете сделать что-то вроде

IntStream.range(0, 10).parallel().boxed()
        .collect(Collectors.filtering(i -> i >= 3 && i < 7, Collectors.toList()));

, что является еще одной причиной, по которой сборщик (в данном случае сборщик toList()) должен быть готов встретиться с пустыми контейнерами, поскольку фильтрация происходит вне реализации Stream, а вызов accept для накопителя составного коллектора не ' t всегда подразумевает вызов accept на накопителе нижнего коллектора.

Требование быть способным обрабатывать пустые контейнеры указано в Collector документации :

Чтобы последовательные и параллельные выполнения приводили к эквивалентным результатам, функции коллектора должны удовлетворять ограничениям identity и ассоциативности .

Ограничение идентичности говорит, что для любого частично накопленного результата объединение его с пустым контейнером результатов должно давать эквивалентный результат. То есть для частично накопленного результата a, который является результатом любой серии вызовов аккумулятора и сумматора, a должен быть эквивалентен combiner.apply(a, supplier.get()).

...