Java 8 Stream API:从基础到高级,掌握流处理的艺术

一、Stream(流)基本介绍

Java 8 API 添加了一个新的抽象称为Stream(流),可以让你以一种声明的方式处理数据,这种风格将要处理的元素集合看做一种流,元素流在管道中传输,并在管道中间的节点上经过中间操作(intermediate operation)的处理(如:筛选,排序,聚合等),最后由最终操作(terminal operation)得到前面处理的结果。

Stream(流)使用一种类似于SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象。

  • 元素是特定类型的对象,形成一个队列;
  • Java中的Stream并不会存储元素,而是按需计算;
  • 数据源可以是集合,数组,I/O channel,产生器generator等;
  • 很多中间操作的方法返回类型就是Stream,因此可以直接连接起来,如下图:

在这里插入图片描述

  • 流的操作不会改变原集合,会生产新的集合,List<String> newList = list.stream().xxx

Stream API 可以极大提高Java程序员的生产力,让程序员写出高效率、干净、整洁的代码。

二、Stream(流)的常用方法

// 准备测试类和数据
public class User{String name;Integer age;
}
List<User> userList = new ArrayList<>();
userList.add(new User("孙悟空", 500));
userList.add(new User("沙悟净", 600));
userList.add(new User("猪八戒", 400));
List<String> letterList = Arrays.asList("a", "", "b", "c", "d", "", "e");
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5);

1、filter(element -> boolean表达式)

  • 过滤元素,将符合boolean表达式的元素保存下来。
// 过滤字母集合,过滤掉空字符串,结果为:["a","b","c","d","e"]
List<String> newLetterList = letterList.stream().filter(str -> !str.isEmpty()).collect(Collectors.toList());

2、distinct

  • 去重,这个方法是元素自身的equals方法来判断其元素是否相等。
userList = userList.stream().distinct().collect(Collectors.toList);

如果这里不重写User类的equals方法,相同的数据不会被处理。

3、sorted() / sorted((T, T) -> int)

  • 对流中的元素进行排序,若流中元素的类有自己的排序规则(即实现了Comparable接口)可直接sorted(),否则需要用sorted((T,T) -> int)说明排序规则。
// 根据年龄大小来排序
userList = userList.stream().sorted((u1,u2) -> u1.getAge() - u2.getAge()).collect(Collectors.toList());// 也可直接替换为方法引用
userList = userList.stream().sorted(Comparator.comparingInt(User::getAge)).collect(Collectors.toList());

4、min、max

  • min() 和 max() 方法,用于查找流中的最小值和最大值。这些方法返回一个 Optional 对象,包含流中的最小或最大元素。

min(Comparator<? super T> comparator) 方法接受一个比较器作为参数,用于定义元素之间的顺序。它会遍历整个流,并返回其中的最小元素。如果流为空,则返回一个空的 Optional 对象。

max(Comparator<? super T> comparator) 方法与 min() 方法类似,唯一的区别是它返回流中的最大元素。同样地,如果流为空,则返回一个空的 Optional 对象。

以下是一个示例,展示如何使用 min() 方法找到一个字符串流中的最短字符串:

List<String> strings = Arrays.asList("apple", "banana", "cherry", "date");
Optional<String> shortestString = strings.stream().min((s1, s2) -> s1.length() - s2.length());
if (shortestString.isPresent()) {System.out.println("Shortest string: " + shortestString.get());
} else {System.out.println("No strings in the list.");
}

在这个例子中,我们首先创建了一个包含四个字符串的列表。然后,使用 stream() 方法将其转换为一个流。接着,调用 min() 方法并传入一个比较器,用于比较两个字符串的长度。最后,使用 isPresent() 方法检查是否找到了最短字符串,并打印结果。

5、summaryStatistics

  • summaryStatistics()它可以对数值型数据流进行统计汇总。这个方法返回一个 IntSummaryStatistics、LongSummaryStatistics 或 DoubleSummaryStatistics 对象,具体取决于流中元素的类型。

以下是一些常见的统计信息:

  • getCount(): 返回流中元素的数量。
  • getSum(): 返回流中所有元素的总和。
  • getAverage(): 返回流中所有元素的平均值。
  • getMin(): 返回流中最小的元素。
  • getMax(): 返回流中最大的元素。

这些方法可以帮助你快速获取流中数值型数据的基本统计信息。

例如,如果你有一个 Stream 对象,想要计算其中所有元素的平均值,你可以这样做:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
double average = numbers.stream().mapToDouble(i -> i).summaryStatistics().getAverage();
System.out.println("Average: " + average);

在这个例子中,我们首先将一个 List 转换为一个 Stream。然后,我们使用 mapToDouble() 方法将每个元素转换为一个 double 类型的值。接着,调用 summaryStatistics() 方法来计算流中所有元素的统计信息。最后,使用 getAverage() 方法获取平均值并打印出来。

同样地,如果你有一个 Stream 对象,想要计算其中所有元素的总和和最小值,你可以这样做:

List<Double> numbers = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
DoubleSummaryStatistics stats = numbers.stream().summaryStatistics();
double sum = stats.getSum();
double min = stats.getMin();
System.out.println("Sum: " + sum);
System.out.println("Min: " + min);

在这个例子中,我们首先将一个 List 转换为一个 Stream。然后,直接调用 summaryStatistics() 方法来计算流中所有元素的统计信息。最后,使用 getSum() 和 getMin() 方法获取总和和最小值并打印出来。

6、limit(long n)

  • 保留处理结果中的前n个元素。
userList = userList.stream().limit(1).collect(Collectors.toList());

7、skip(long n)

  • 去除(跳过)处理结果中的前n个元素。
// 从处理结果中保留2个元素,再从保留的2个元素中去除第1个元素
userList = userList.stream().limit(2).skip(1).collect(Collectors.toList());// 从处理结果中去除2个元素后,再保留1个元素
userList = userList.stream().skip(2).limit(1).collect(Collectors.toList());

8、map(T -> R)

  • 将流中的每一个元素映射为R。

map方法接收一个lambda表达式,这个表达式是一个函数,输入类型是集合元素的类型,输出类型是任意类型,即你可以选择将元素映射为任意类型,并对映射后的值做下一步处理。

// 将集合中的每个元素+2,输出结果:[3,4,5,6,7]
numberList = numberList.stream().map(i -> i+2).collect(Collectors.toList());
// 将用户的name、age分别保存到新集合中
List<String> nameList = list.stream().map(Person::getName).collect(Collectors.toList());
List<Integer> ageList = list.stream().map(Person::getAge).collect(Collectors.toList());
// 将用户的属性取出做进一步处理
userList = userList.stream().map(u -> {u.setAge(u.getAge() + 1);u.setName("孙悟空".equals(p.getName()) ? "悟空" : "西游人物");return p;
}).collect(Collectors.toList());

9、flatMap(T -> Stream)

  • flatMap的用法和map类似,它们都接受一个函数作为参数,用于对流中的每个元素进行转换。

map和flatMap的区别:

  • map()操作将每个元素转换成一个新元素,并将所有这些新生成的元素收集到一个新的流中。
  • flatMap()操作将每个元素转换成一个新的流,并将所有这些新生成的流合并成一个单一的流。

flatMap()和map()之间还有一个重要的区别,那就是flatMap()支持处理包含嵌套数据结构的流。

在Java中,如果你有一个泛型类型中的数据本身也是一个泛型类型,例如List<List>,那么使用map()操作时,你可能会遇到一些困难。因为map()操作只会对最外层的元素进行转换,而不会深入到嵌套的数据结构中。

但是,flatMap()操作可以很好地处理这种情况。它可以将每个元素转换成一个流,并将所有这些流合并成一个单一的流。这样,你就可以在处理嵌套数据结构时使用flatMap()操作。

示例:首先使用stream()方法将userPlus转换为一个流,由于userPlus是一个嵌套的集合,所以我们需要使用flatMap()操作来将其展平成一个单一的流,接下来,我们使用map()操作来从每个User对象中提取出其name属性,并将结果转换成一个新的流,再使用distinct()操作来去除重复的名字,最后,我们使用collect()操作将流收集到一个新的List中,这个列表包含了所有唯一的用户名。

List<User> user1 = new ArrayList<>();
user1.add(new User("A",23));
user1.add(new User("B",23));List<User> user2 = new ArrayList<>();
user2.add(new User("C",23));
user2.add(new User("D",23));List<List<User>> userPlus = new ArrayList<>();
userPlus.add(user1);
userPlus.add(user2);// 最后输出的结果是:["A","B","C","D"]
List<String> nameList = userPlus.stream().flatMap(t -> t.stream()).map(t -> t.getName()).distinct().collect(Collectors.toList());

10、reduce((T, T) -> T) / reduce(T, (T, T) -> T)

  • reduce操作可以用来将流中的元素组合成一个单一的结果,可以用来执行各种聚合操作。

这个操作有两种重载形式:

  • reduce((T, T) -> T):这种形式的reduce接受一个二元操作符(即一个函数),该函数将两个元素合并成一个新元素。这个过程会一直重复,直到流中的所有元素都被合并成一个单一的结果。
  • reduce(T, (T, T) -> T): 这种形式的reduce()除了接受一个二元操作符外,还接受一个初始值。如果流为空,初始值将直接作为结果返回;否则,初始值将与流中的第一个元素合并,产生一个新的中间结果,然后再与流中的下一个元素合并,以此类推,直到流中的所有元素都被处理完毕。
// 计算年龄总和
int sum = personList.stream().map(Person::getAge).reduce(0, (a, b) -> a + b);
// 计算年龄总和
int sum = personList.stream().map(Person::getAge).reduce(0, Integer::sum);// 价格使用BigDecimal防止精度丢失,将所有商品的价格累加
BigDecimal totalPrice = goodList.stream().map(GoodsCode::getPrice).reduce(BigDecimal.ZERO, BigDecimal::add);

11、anyMatch(T -> boolean表达式)

  • 流中是否有元素满足这个Boolean表达式
// 集合中是否存在一个元素的age等于500
boolean b = userList.stream().anyMatch(u-> u.getAge() == 500);

12、allMatch(T -> boolean) 和 noneMatch(T -> boolean)

  • allMatch(T -> boolean),即流中所有元素是否都满足boolean表达式。
  • noneMatch(T -> boolean),即是否流中没有一个元素满足boolean表达式。

可以配合filter一起使用。

示例:下面示例中准备了一个tagList集合中有标签A、B、C,3个元素,还有一个tagMap,其中key是序号,value是标签集合,现想获取tagMap中对应标签集合(value)和tagList集合完全不匹配的元素对应的序号(key)。

List<String> tagList = new ArrayList<>();
tagList.add("A");
tagList.add("B");
tagList.add("C");Map<Integer, List<String>> tagMap = new HashMap<>();
List<String> list1 = new ArrayList<>();
list1.add("A");
list1.add("D");
List<String> list2 = new ArrayList<>();
list2.add("B");
List<String> list3 = new ArrayList<>();
list3.add("D");
tagMap.put(1, list1);
tagMap.put(2, list2);
tagMap.put(3, list3);
// 返回结果:[3]
List<Integer> collect = map.entrySet().stream().filter(entry -> list.stream().noneMatch(s -> entry.getValue().contains(s))).map(Map.Entry::getKey).collect(Collectors.toList());

13、count()

  • 返回流中元素的个数,返回long型。
int countOfAdult=persons.stream().filter(p -> p.getAge() > 18).map(person -> new Adult(person)).count();

14、forEach()

  • 普通for循环或者增强for循环,break跳出整个循环,continue结束本次循环。Stream的forEach处理集合时需要使用关键字return跳出本次循环,并执行下次遍历(不能跳出整个流的forEach循环)。

它接受一个消费函数作为参数,该函数将被应用于流中的每个元素。

// 示例 1:打印流中的所有元素
letterList.stream().map(String::toUpperCase).forEach(System.out::println);// 示例 2:将字符串流中的所有元素转换为大写并打印
letterList.stream().map(String::toUpperCase).forEach(System.out::println);

forEach()操作是终端操作之一,它不能被用于中间操作。也就是说,调用forEach()后,你不能再对流进行其他操作。另外,forEach()操作通常用于打印或其他副作用,而不是构建新的流或集合。

15、peek()

  • peek() 是一个中间操作方法,它允许你在不影响流的主要处理逻辑的情况下,查看或使用流中的每个元素。这个方法可以用来进行一些调试或日志记录等操作。

peek() 方法的签名如下:Stream<T> peek(Consumer<? super T> action)

其中,action 是一个 Consumer 函数,用于对流中的每个元素进行操作。这个函数不会改变流中的元素,也不会返回任何值。

以下是一个简单的示例,演示如何使用 peek() 方法来打印流中的每个元素:

List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape");fruits.stream().filter(f -> f.length() > 5).peek(System.out::println).collect(Collectors.toList());

在这个例子中,我们首先创建了一个包含四个水果的列表。然后,使用 stream() 方法将其转换为一个流。接着,调用 filter() 方法来过滤出长度大于 5 的水果。然后,使用 peek() 方法在处理流中的元素时打印每个元素。最后,使用 collect() 方法将剩余的元素收集到一个新的列表中。

注意事项:

  • peek()不会改变流的结果:无论你在 peek() 方法中做了什么操作,流的最终结果都不会受到影响。
  • peek()可能会被无限次调用:如果你在流的中间操作中使用了 peek(),那么在每次中间操作时,peek() 都会被调用。
  • peek()是非短路操作:与 forEach() 不同,peek() 不是终端操作,它不会使流处理短路。也就是说,所有的中间操作都会被执行完毕,包括 peek()。
  • peek()可能会影响流的性能:如果在 peek() 方法中执行了非常耗时的操作,那么可能会影响流的整体性能。

peek() 主要适用于以下场景:

  • 调试:可以用来打印流中的元素,帮助你理解流的处理过程。
  • 日志记录:可以用来记录流中的元素,例如在处理大数据集时,记录每个处理的元素。
  • 副作用操作:可以用来执行一些副作用操作,例如更新数据库或发送通知等。

需要注意的是,虽然 peek() 提供了一个方便的方式来查看流中的元素,但它不应该被用于实际的业务逻辑中。因为它的主要目的是为了调试和日志记录,而不是处理流的主要逻辑。

16、Stream.iterate

java.util.stream.Stream下共有两个iterate,都是 Java 8 中引入的 Stream API 方法,用于生成无限流。它们的主要区别在于第二个方法允许你指定一个条件来决定何时停止生成元素。

方法一:

iterate(T seed, final UnaryOperator<T> f)

这个方法接受两个参数,会不断地应用函数 f 到前一个元素上,生成一个无限流。

  • seed:流的初始元素。
  • f:一个函数,用于将前一个元素转换为下一个元素。

例如,我们可以使用这个方法来生成自然数流:

// 这将输出从 1 到 10 的自然数。
Stream.iterate(1, x -> x + 1).limit(10).forEach(System.out::println);

方法二:

iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> f)

这个方法接受三个参数,会不断地应用函数 f 到前一个元素上,并使用 hasNext 函数来检查是否应该继续生成下一个元素。如果 hasNext 返回 false,则流将结束。

  • seed:流的初始元素。
  • hasNext:一个谓词函数,用于判断是否应该继续生成下一个元素。
  • f:一个函数,用于将前一个元素转换为下一个元素。

例如,我们可以使用这个方法来生成小于等于 10 的自然数流:

// 这将输出从 1 到 10 的自然数。
Stream.iterate(1, x -> x <= 10, x -> x + 1).forEach(System.out::println);

总的来说,第二个方法提供了更灵活的方式来控制流的生成过程,可以根据特定的条件来决定何时停止生成元素。

在执行 Stream.iterate 时并没有生成具体数据,只是产生了一个流,只有在使用时才会有数据。

三、收集方法collect()详解

收集流中元素的方法,传参是一个收集器接口Collectors,下面是Collectors中的方法:

在这里插入图片描述
下面我们逐个介绍这些方法的使用。

1、将流中的元素放到集合中

  • .collect(Collectors.toCollection(Supplier<C> collectionFactory));
//源码    
public static <T, C extends Collection<T>> Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {return new CollectorImpl<>(collectionFactory, Collection<T>::add,(r1, r2) -> { r1.addAll(r2); return r1; },CH_ID);
}
//使用示例
LinkedList<Integer> collect = userList.stream().map(User::getAge).collect(Collectors.toCollection(LinkedList::new));
  • toList()、toSet()
List<Integer> list = userList.stream().map(User::getAge).collect(Collectors.toList());Set<String> nameSet = userList.stream().map(User::getName).collect(Collectors.toSet());

2、分组

  • groupingBy:将处理后的元素进行分组,得到一个Map集合,它有三个重载方法:
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function classifier) {return groupingBy(classifier, toList());
}public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function classifier, Collector downstream) {return groupingBy(classifier, HashMap::new, downstream);
}public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function classifier, Supplier mapFactory, Collector downstream) {// ......
}

第一个方法只需一个分组参数classifier,内部自动将结果保存到一个map中,每个map的键为 ‘?’ 类型(即classifier的结果类型),值为一个list,这个list中保存在属于这个组的元素。

但是它实际是调用了第二个方法-- Collector 默认为list。

而第二个方法实际是调用第三个方法,默认Map的生成方式为HashMap。

第三个方法才是真实完整的分组逻辑处理。

下面是一些实际的案例:

//数据准备
@Data
@AllArgsConstructor
public class Books {private Integer id;private Integer num;private String name;private Double price;private String category;
}Books book1 = new Books(1,100,"Java入门",60.0,"互联网类") ;
Books book2 = new Books(2,200,"Linux私房菜",100.0,"互联网类") ;
Books book3 = new Books(3,200,"Docker进阶",70.0,"互联网类") ;
Books book4 = new Books(4,600,"平凡的世界",200.0,"小说类") ;
Books book5 = new Books(5,1000,"白鹿原",190.0,"小说类") ;
List<Books> booksList = Lists.newArrayList(book1,book2,book3,book4,book5);
  • case1:按照某个属性分组,即以该属性为Map集合的key,把这个属性相同的对象放在一个List集合中做为value。
//按照category分类
Map<String,List<Books>> map = booksList.stream().collect(Collectors.groupingBy(Books::getCategory));
// 运行结果
{
互联网类=[Books(id=1, num=100, name=Java入门, price=60.0, category=互联网类), Books(id=2, num=200, name=Linux私房菜, price=100.0, category=互联网类), Books(id=3, num=200, name=Docker进阶, price=70.0, category=互联网类)],
小说类=[Books(id=4, num=600, name=平凡的世界, price=200.0, category=小说类), Books(id=5, num=1000, name=白鹿原, price=190.0, category=小说类)]
}
  • case2: 按照某几个属性拼接分组
Map<String,List<Books>> map = booksList.stream().collect(Collectors.groupingBy(t -> t.getCategory() +"_" + t.getName()));
// 运行结果
{
互联网类_Linux私房菜=[Books(id=2, num=200, name=Linux私房菜, price=100.0, category=互联网类)],
小说类_平凡的世界=[Books(id=4, num=600, name=平凡的世界, price=200.0, category=小说类)],
互联网类_Docker进阶=[Books(id=3, num=200, name=Docker进阶, price=70.0, category=互联网类)],
互联网类_Java入门=[Books(id=1, num=100, name=Java入门, price=60.0, category=互联网类)],
小说类_白鹿原=[Books(id=5, num=1000, name=白鹿原, price=190.0, category=小说类)]
}
  • case3: 按照不同的条件分组
//不同条件下,使用不同的key
Map<String,List<Books>> map = booksList.stream().collect(Collectors.groupingBy(t -> {if(t.getNum() > 500){return "数量充足";}else{return "数量较少";}}));
// 运行结果
{
数量充足=[Books(id=4, num=600, name=平凡的世界, price=200.0, category=小说类), Books(id=5, num=1000, name=白鹿原, price=190.0, category=小说类)],
数量较少=[Books(id=1, num=100, name=Java入门, price=60.0, category=互联网类), Books(id=2, num=200, name=Linux私房菜, price=100.0, category=互联网类), Books(id=3, num=200, name=Docker进阶, price=70.0, category=互联网类)]
}
  • case4:实现多级分组,即由双参数版本的Collectors.groupingBy,对由第一个参数分类后的结果再进行分类,此时结果类型。
//接case3,想先按照类别分组,再给每个组按照数量再分一次
Map<String,Map<String,List<Books>>> map = booksList.stream().collect(Collectors.groupingBy(t -> t.getCategory(), Collectors.groupingBy( t -> {if(t.getNum() > 100){return "数量充足";}else{return "数量较少";}})));
// 运行结果
{
互联网类={数量充足=[Books(id=2, num=200, name=Linux私房菜, price=100.0, category=互联网类), Books(id=3, num=200, name=Docker进阶, price=70.0, category=互联网类)], 数量较少=[Books(id=1, num=100, name=Java入门, price=60.0, category=互联网类)]}, 
小说类={数量充足=[Books(id=4, num=600, name=平凡的世界, price=200.0, category=小说类), Books(id=5, num=1000, name=白鹿原, price=190.0, category=小说类)]}
}
  • case5:分组后,统计每个分组中元素的个数,Map集合的value类型为long型
Map<String,Long> map = booksList.stream().collect(Collectors.groupingBy(Books::getCategory,Collectors.counting()));
// 运行结果
{互联网类=3, 小说类=2}
  • case6:分组后,统计每个分组中元素的某属性的总和
Map<String,Integer> map = booksList.stream().collect(Collectors.groupingBy(Books::getCategory,Collectors.summingInt(Books::getNum)));
// 运行结果
{互联网类=500, 小说类=1600}
  • case7: 加比较器取某属性最值
Map<String,Books> map3 = booksList.stream().collect(Collectors.groupingBy(Books::getCategory, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Books::getNum)), Optional::get)));
// 运行结果
{互联网类=Books(id=2, num=200, name=Linux私房菜, price=100.0, category=互联网类), 小说类=Books(id=5, num=1000, name=白鹿原, price=190.0, category=小说类)}
  • case8:联合其他收集器
Map<String, Set<String>> map2 = booksList.stream().collect(Collectors.groupingBy(Books::getCategory,Collectors.mapping(t->t.getName(),Collectors.toSet())));
// 运行结果
{互联网类=[Linux私房菜, Docker进阶, Java入门], 小说类=[平凡的世界, 白鹿原]}  
  • groupingByConcurrent

返回一个并发Collector收集器对T类型的输入元素执行"group by"操作, 也有三个重载的方法, 其使用与groupingBy 基本相同。

  • partitioningBy

该方法将流中的元素按照给定的校验规则的结果分为两个部分,放到一个map中返回,map的键是Boolean类型,值为元素的集合。

//源码(两个重载方法)public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {return partitioningBy(predicate, toList());}public static <T, D, A> Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,Collector<? super T, A, D> downstream) {......}//从上面的重载方法中可以看出,partitioningBy 与 groupingBy 类似, 只不过partitioningBy 生成的map的key的类型限制只能是Boolean类型。//示例
Stream<Student> stream = studentList.stream();Map<Boolean, List<Student>> m4 = stream.collect(Collectors.partitioningBy(stu -> stu.getScore() > 60));Map<Boolean, Set<Student>> m5 = stream.collect(Collectors.partitioningBy(stu -> stu.getScore() > 60, Collectors.toSet()));

3、将流中的元素放到Map中

  • .collect(Collectors.toMap(x,x,x))

toMap方法是根据给定的键生成器和值生成器生成的键和值保存到一个map中返回,键和值的生成都依赖于元素,可以指定出现重复键时的处理方案和保存结果的map。

//源码(三个重载方法)
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function keyMapper,Function valueMapper) {return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function keyMapper,Function valueMapper,BinaryOperator mergeFunction) {return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function keyMapper,Function valueMapper,BinaryOperator mergeFunction,Supplier mapSupplier){......
}//三个重载的方法,最终都是调用第三个方法来实现, 第一个方法中默认指定了key重复的处理方式和map的生成方式; 而第二个方法默认指定了map的生成方式,用户可以自定义key重复的处理方式。//示例
Map<Integer, Student> map1 = stream.collect(Collectors.toMap(Student::getId, v->v));
Map<Integer, String> map2 = stream.collect(Collectors.toMap(Student::getId, Student::getName, (a, b)->a));
Map<Integer, String> map3 = stream.collect(Collectors.toMap(Student::getId, Student::getName, (a, b)->a, HashMap::new));

4、元素拼接joining

  • joining() : 没有分隔符和前后缀,直接拼接
  • joining(CharSequence delimiter) : 指定元素间的分隔符
  • joining(CharSequence delimiter,CharSequence prefix, CharSequence suffix): 指定分隔符和整个字符串的前后缀。
String s = list.stream().map(Person::getName).collect(joining());
//结果:jackmiketomString s = list.stream().map(Person::getName).collect(joining(","));
//结果:jack,mike,tomString s = list.stream().map(Person::getName).collect(joining(",", "name:", "1"));
//结果:name:jack1,name:mike1,name:tom1

5、类型转换

mapping:这个映射是首先对流中的每个元素进行映射,即类型转换,然后再将新元素以给定的Collector进行归纳。 类似与Stream的map方法。

collectingAndThen:在归纳动作结束之后,对归纳的结果进行再处理。

Stream<Student> stream = studentList.stream();List<Integer> idList = stream.collect(Collectors.mapping(Student::getId, Collectors.toList()));Integer size = stream.collect(Collectors.collectingAndThen(Collectors.mapping(Student::getId, Collectors.toList()), o -> o.size()));

6、聚合

  • counting: 同 stream.count()
  • minBy: 同stream.min()
  • maxBy: 同stream.max()
  • summingInt:
  • summingLong:
  • summingDouble:
  • averagingInt:
  • averagingLong:
  • averagingDouble:
Long count = stream.collect(Collectors.counting());
stream.count();
stream.collect(Collectors.minBy((a,b)-> a.getId() - b.getId()));
stream.min(Comparator.comparingInt(Student::getId));
stream.collect(Collectors.summarizingInt(Student::getId));
stream.collect(Collectors.summarizingLong(Student::getTimeStamp));
stream.collect(Collectors.averagingDouble(Student::getScore));

7、reducing

reducing方法有三个重载方法,其实是和Stream里的三个reduce方法对应的,二者是可以替换使用的,作用完全一致,也是对流中的元素做统计归纳作用。

//源码(三个重载方法)
public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) {......
}public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) {......
}public static <T, U> Collector<T, ?, U> reducing(U identity,Function mapper, BinaryOperator<U> op) {......
}//示例
List<String> list2 = Arrays.asList("123","456","789","qaz","wsx","edc");Optional<Integer> optional = list2.stream().map(String::length).collect(Collectors.reducing(Integer::sum));Integer sum1 = list2.stream().map(String::length).collect(Collectors.reducing(0, Integer::sum));Integer sum2 = list2.stream().limit(4).collect(Collectors.reducing(0, String::length, Integer::sum));//拓展:实际运用中,可能会用到比较复杂的 groupingBy、mapping、toMap 嵌套、组合使用,进行多级分组处理数据。如: 
Stream<Student> stream = studentList.stream();
// 根据score分组,并提取ID作为集合元素
Map<Double, List<Integer>> map1 = stream.collect(Collectors.groupingBy(Student::getScore, Collectors.mapping(Student::getId, Collectors.toList())));
// 根据score分组, 并将ID和name组成map作为元素
Map<Double, Map<Integer, String>> map2 = stream.collect(Collectors.groupingBy(Student::getScore, Collectors.toMap(Student::getId, Student::getName)));
// 先根据score分组,再根据name进行二次分组
Map<Double, Map<String, List<Student>>> map3 = stream.collect(Collectors.groupingBy(Student::getScore, Collectors.groupingBy(Student::getName)));//当然也可以根据我们想要的条件,设置分组的组合条件,只需要替换 Student::getScore ,换成我们想要的条件即可, 如: 
Map<String, List<Integer>> map3 = stream.collect(Collectors.groupingBy(stu -> {if (stu.getScore() > 60) {return "PASS";} else {return "FAIL";}
}, Collectors.mapping(Student::getId, Collectors.toList())));
//按照这种思路,我们可以随意处理stream中的元素成我们想要的结果数据。

四、并行流parallelStream

1、parallelStream

  • 每个Stream都有两种模式:顺序执行和并行执行,调用parallelStream()和stream()方法,返回的都是一个流;

  • 并行流的创建可以通过:xx.parallelStream() 或 xx.stream().parallel();

顾名思义,当使用顺序方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数组会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。

  • parallelStream原理:
List originalList = someData;
split1 = originalList(0, mid);//将数据分小部分
split2 = originalList(mid,end);
new Runnable(split1.process());//小部分执行操作
new Runnable(split2.process());
List revisedList = split1 + split2;//将结果合并

大家对hadoop有稍微了解就知道,里面的 MapReduce 本身就是用于并行处理大数据集的软件框架,其 处理大数据的核心思想就是大而化小,分配到不同机器去运行map,最终通过reduce将所有机器的结果结合起来得到一个最终结果,与MapReduce不同,Stream则是利用多核技术可将大数据通过多核并行处理,而MapReduce则可以分布式的。

  • parallelStream默认的并发线程数比CPU处理器的数量少1个(最优策略是每个CPU处理器分配一个线程,然而主线程也算一个线程)
// 获取当前机器CPU处理器的数量
System.out.println(Runtime.getRuntime().availableProcessors());// 输出 6
// parallelStream默认的并发线程数
System.out.println(ForkJoinPool.getCommonPoolParallelism());// 输出 5
// 设置全局并行流并发线程数
//这是全局配置,会影响所有的并行流
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "3");
  • parallelStream是线程不安全的
List<Integer> listStream = new ArrayList<>();
List<Integer> listParallelStream = new ArrayList<>();
IntStream.range(0, 1000).forEach(t -> listStream.add(t));
IntStream.range(0, 1000).parallel().forEach(listParallelStream::add);
System.out.println("listStream size:" + listStream.size());
System.out.println("listParallelStream size:" + listParallelStream.size());
// 输出结果
listStream size:1000
listParallelStream size: 969
  • 使用并行流遍历打印一个集合元素,并输出当前线程,可以看到线程抬头是ForkJoinPool,且遍历输出的元素是无序的。

在这里插入图片描述

  • 并发不一定就能提高性能,CPU资源不足,存在频繁的线程切换反而会降低性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/57044.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云黑系统全解无后门 +搭建教程

这套系统呢是玖逸之前南逸写的一套云黑系统&#xff0c;功能带有卡密生成和添加黑名单等&#xff0c;源码放在我的网盘里已经两年之久&#xff0c;由于玖逸现在已经跑路了所以现在发出来分享给大家&#xff0c;需要的可以自己拿去而开&#xff0c;反正功能也不是很多具体的自己…

电脑视频剪辑大比拼,谁更胜一筹?

随着短视频的火爆&#xff0c;越来越多的人开始尝试自己动手制作视频&#xff0c;无论是记录生活点滴还是创作个性短片&#xff0c;一款好用的视频剪辑软件是必不可少的。今天&#xff0c;我们就从短视频运营的角度&#xff0c;来聊聊几款热门的电脑视频剪辑软件&#xff0c;看…

docker配置加速器

阿里云 控制台》容器镜像服务》镜像工具》镜像加速器 复制地址&#xff1a;https://ywtoq7bz.mirror.aliyuncs.com 到&#xff1a;etc/docker下&#xff1a;vi daemon.json 格式&#xff1a; { "registry-mirrors": ["加速器地址"] } 注&#xff1…

JavaScript:闭包、防抖与节流

一&#xff0c;闭包 1&#xff0c;什么是闭包 闭包是指一个函数和其周围的词法环境(lexical environment)的组合。 换句话说&#xff0c;闭包允许一个函数访问并操作函数外部的变量。 闭包的核心特性: 函数内部可以访问外部函数的变量即使外部函数已经返回&#xff0c;内部…

Centos7 安装部署Zookeeper

官网地址&#xff1a;https://zookeeper.apache.org/ 1. Zookeeper 介绍 Apache ZooKeeper是一个开源的分布式协调服务&#xff0c;它用于维护配置信息、命名、提供分布式同步以及提供组服务等。ZooKeeper的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来&#x…

一款新开源跨平台的.NET Word(docx)模版导出引擎,完美支持Linux和Mac操作系统(附源码)

前言 在数字化办公日益盛行的今天&#xff0c;文档处理成为了我们日常工作不可或缺的一部分。然而&#xff0c;许多传统的文档处理工具都依赖于特定的操作系统和复杂的组件安装&#xff0c;这无疑给跨平台办公带来了诸多不便。为了解决这一问题&#xff0c;我们找到了一个新的…

【MR开发】在Pico设备上接入MRTK3(一)——在Unity工程中导入MRTK3依赖

写在前面的话 在Pico上接入MRTK3&#xff0c;目前已有大佬开源。 https://github.com/Phantomxm2021/PicoMRTK3 也有值得推荐的文章。 MRTK3在PICO4上的使用小结 但由于在MacOS上使用MRTK3&#xff0c;无法通过Mixed Reality Feature Tool工具管理MRTK3安装包。 故记录一下…

◇【论文_20151120_20160405v3】Dueling Network 决斗〔Google DeepMind〕

整理代码&#xff1a;Dueling_DQN__Pendulum_v1.ipynb https://arxiv.org/abs/1511.06581 Dueling Network Architectures for Deep Reinforcement Learning 文章目录 摘要1. 引言1.1. 相关工作 2. 背景2.1. Deep Q-networks 【DQN】2.2. Double Deep Q-networks 【DDQN】2.3…

OpenCV高级图形用户界面(13)选择图像中的一个矩形区域的函数selectROI()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 允许用户在给定的图像上选择一个感兴趣区域&#xff08;ROI&#xff09;。 该功能创建一个窗口&#xff0c;并允许用户使用鼠标来选择一个 ROI。…

Apache Cordova学习计划

Apache Cordova&#xff08;之前称为 PhoneGap&#xff09;&#xff1a; 1. PhoneGap的起源&#xff1a;2008年8月&#xff0c;PhoneGap在旧金山的iPhoneDevCamp上首次亮相&#xff0c;由Nitobe公司开发&#xff0c;目的是“为跨越Web技术和iPhone之间的鸿沟牵线搭桥”。 2. Ph…

其他css的用途

1.animation-fill-mode: backwards; //避免了在动画开始前元素的突然显现&#xff0c;动画必要。 2.用rem响应式字体大小&#xff0c;可以在html样式定义font-size?(例10px&#xff0c;62.5%(100%是16px))。然后样式就可以用rem代替px。 3.color: transparent;: 这行代码将文…

UDP协议和TCP协议

UDP协议&#xff1a; 是一种无连接的、简单的传输层通信协议&#xff0c;它在IP协议&#xff08;网络层&#xff09;之上提供服务。 特点&#xff1a; 无连接&#xff1a;在数据传输前&#xff0c;发送方和接收方之间不需要建立连接&#xff0c;可以直接发送数据。 简单&…

计算生物学与生物信息学漫谈-2-测序深度/读长质量和Fasta处理

上一篇文章中我们介绍了测序技术的由来与发展&#xff0c;那么在介绍第三代测序的时候&#xff0c;我们提到了关于测序深度和读长的问题&#xff0c;那么本篇文章就详解介绍一下。 计算生物学与生物信息学漫谈-1-测序一路走来-CSDN博客 目录 1.测序深度SEQUENCING DEPTH &…

《AI生成式工具使用》之:自助生成视频

目录 背景说明及目标&#xff1a; 实现过程&#xff1a; 1、有问题找度娘 2、利用剪映AI生成视频具体步骤 剪映AI感受 3、利用万彩AI生成视频具体步骤 万彩AI感受 4、利用腾讯云剪生成视频具体步骤 腾讯云剪感受 最终结论&#xff1a; 关注我&#xff0c;躺不平就一起…

【部署篇】RabbitMq-02单机模式部署

RabbitMQ和Erlang/OTP兼容性矩阵 下表提供了当前支持的RabbitMQ版本系列的Erlang兼容性矩阵。更多RabbitMQ版本&#xff0c;请参阅官网的系列兼容性列表。官网地址&#xff1a;https://www.rabbitmq.com/docs/which-erlang RabbitMQ版本最小支持版本最大支持版本备注 4.0.24.…

uniapp 单表、多级动态表单添加validateFunction自定义规则

uniapp 多级动态表单添加自定义规则 在uniapp制作小程序时&#xff0c;当涉及到需要设置validateFunction的校验规则时。可能遇到的问题 1、validateFunction不生效&#xff0c;没有触发 2、多层级表单怎么添加validateFunction自定义校验规则 本文将以单表单校验和多表单校…

Axure重要元件三——中继器添加数据

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 本节课&#xff1a;中继器添加数据 课程内容&#xff1a;添加数据项、自动添加序号、自动添加数据汇总 应用场景&#xff1a;表单数据的添加 案例展示&#xff1a; 步骤…

力扣(leetcode)每日一题 3192 使二进制数组全部等于 1 的最少操作次数 II

3192. 使二进制数组全部等于 1 的最少操作次数 II 题干 给你一个二进制数组 nums 。 你可以对数组执行以下操作 任意 次&#xff08;也可以 0 次&#xff09;&#xff1a; 选择数组中 任意 一个下标 i &#xff0c;并将从下标 i 开始一直到数组末尾 所有 元素 反转 。 反转…

经验是最坏的老师

奥斯卡.王尔德说过&#xff1a;经验是最坏的老师。他经常先考试&#xff0c;然后再给出指导。 这让我想起了另外一句话&#xff1a;愚笨的人&#xff0c;往往都在犯同样的错误&#xff1b;普通的人&#xff0c;从自己的错误中学习&#xff1b;聪明人从别人的错误中学习。 如果…

Linux 防火墙的开启、关闭、禁用命令

Linux 防火墙的开启、关闭、禁用命令 文章目录 Linux 防火墙的开启、关闭、禁用命令1.设置开机启用防火墙2.设置开机禁用防火墙3.启动防火墙4.关闭防火墙5.检查防火墙状态 1.设置开机启用防火墙 systemctl enable firewalld.service2.设置开机禁用防火墙 systemctl disable f…