什么时候使用collect()
vs reduce()
?有没有人有好的,具体的例子说明何时最好以一种或另一种方式走?
Javadoc提到collect()是一个可变的reduce。
鉴于这是一个可变的减少,我假设它需要同步(内部),这反过来可能对性能造成不利影响。大概reduce()
更容易并行化,但需要在精简的每个步骤之后都必须创建新的数据结构以返回以作为代价。
上面的陈述是猜测,不过,我很乐意在此鸣叫。
什么时候使用collect()
vs reduce()
?有没有人有好的,具体的例子说明何时最好以一种或另一种方式走?
Javadoc提到collect()是一个可变的reduce。
鉴于这是一个可变的减少,我假设它需要同步(内部),这反过来可能对性能造成不利影响。大概reduce()
更容易并行化,但需要在精简的每个步骤之后都必须创建新的数据结构以返回以作为代价。
上面的陈述是猜测,不过,我很乐意在此鸣叫。
Answers:
reduce
是“ 折叠 ”操作,它将二进制运算符应用于流中的每个元素,其中运算符的第一个参数是前一个应用程序的返回值,第二个参数是当前流元素。
collect
是一个聚合操作,其中创建一个“集合”,并将每个元素“添加”到该集合。然后将流中不同部分的集合添加到一起。
如果我们想获取字符串流并将它们连接成单个长字符串,则可以通过普通归约来实现:
String concatenated = strings.reduce("", String::concat)
我们将获得理想的结果,甚至可以并行工作。但是,我们可能对性能不满意!这样的实现将进行大量的字符串复制,并且运行时间的字符数将为O(n ^ 2)。一种更高效的方法是将结果累积到StringBuilder中,该StringBuilder是用于累积字符串的可变容器。我们可以使用与普通归约相同的技术来并行化可变归约。
因此,关键是在两种情况下并行化都是相同的,但是在这种reduce
情况下,我们将函数应用于流元素本身。在这种collect
情况下,我们将该函数应用于可变容器。
int
是不可变的,因此您不能轻易使用收集操作。您可以像使用AtomicInteger
或某些自定义一样进行肮脏的破解,IntWrapper
但是为什么呢?折叠操作与收集操作完全不同。
reduce
方法,您可以在其中返回与流元素不同类型的对象。
原因很简单:
collect()
只能与可变结果对象一起使用。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(Integer.valueOf(6), sum);
}
collect()
具有可变的”示例例如,如果您想使用collect()
它来手动计算总和,则不能使用它,BigDecimal
而只能使用MutableInt
from 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
没有空的构造函数,因此无法正常工作)
Integer
构造函数(new Integer(6)
),在更高的Java版本中已弃用。
Integer.valueOf(6)
StringBuilder
可变的。请参阅:hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/...
正常归约意味着将两个不变值(例如int,double等)组合起来并产生一个新的值。这是一成不变的减少。相比之下,collect方法旨在对容器进行变异以累积其应产生的结果。
为了说明问题,让我们假设您想Collectors.toList()
使用一个简单的简化来实现
List<Integer> numbers = stream.reduce(
new ArrayList<Integer>(),
(List<Integer> l, Integer e) -> {
l.add(e);
return l;
},
(List<Integer> l1, List<Integer> l2) -> {
l1.addAll(l2);
return l1;
});
这相当于Collectors.toList()
。但是,在这种情况下,您会更改List<Integer>
。如我们所知,ArrayList
它不是线程安全的,在迭代时从中添加/删除值也不安全,因此您将获得并发异常或ArrayIndexOutOfBoundsException
在更新列表或组合器时或任何类型的异常(尤其是在并行运行时)尝试合并列表,因为您是通过将整数累加(添加)来使列表变异的。如果要使此线程安全,则每次都需要传递一个新列表,这会影响性能。
相反,Collectors.toList()
作品以类似的方式。但是,当您将值累加到列表中时,它可以保证线程安全。从方法的文档中collect
:
使用收集器对此流的元素执行可变还原操作。如果流是并行的,并且收集器是并发的,并且流是无序的或收集器是无序的,则将执行并发缩减。当并行执行时,可以实例化,填充和合并多个中间结果,以保持可变数据结构的隔离。 因此,即使与非线程安全数据结构(例如ArrayList)并行执行时,也不需要其他同步来进行并行缩减。
因此,回答您的问题:
什么时候使用
collect()
vsreduce()
?
如果你有不可变的值,例如ints
,doubles
,Strings
再进行正常降噪工程就好了。但是,如果必须将reduce
您的值转换为List
可变数据结构,则需要使用该collect
方法的可变约简。
x
线程,每个线程“添加至身份”然后组合在一起。好的例子。
public static void main(String[] args) { List<Integer> l = new ArrayList<>(); l.add(1); l.add(10); l.add(3); l.add(-3); l.add(-4); List<Integer> numbers = l.stream().reduce( new ArrayList<Integer>(), (List<Integer> l2, Integer e) -> { l2.add(e); return l2; }, (List<Integer> l1, List<Integer> l2) -> { l1.addAll(l2); return l1; });for(Integer i:numbers)System.out.println(i); } }
我尝试并没有得到CCm例外
它们在运行期间的潜在内存占用量非常不同。在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 ,因为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();
System.out.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
Reduce函数处理两个参数,第一个参数是流中的前一个返回值,第二个参数是流中的当前计算值,它将下一个计算中的第一个值和当前值相加为第一个值。
根据文档
当在groupingBy或partitioningBy的下游进行多级归约时,reducing()收集器最有用。要对流执行简单的还原,请改用Stream.reduce(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操作始终返回新值。但是,累加器函数每次处理流的元素时也会返回一个新值。假设您想将流的元素简化为一个更复杂的对象,例如集合。这可能会妨碍您的应用程序的性能。如果您的reduce操作涉及将元素添加到集合中,那么每次累加器函数处理一个元素时,它都会创建一个包含该元素的新集合,这效率很低。相反,对您而言,更新现有集合会更有效。您可以使用Stream.collect方法执行此操作,下一部分将对其进行介绍...
因此,在减少的情况下可以“重用”身份,因此,.reduce
如果可能的话,可以稍微提高效率。