JDK1.8 新特性(二)【Stream 流】

前言

        上节我们学了 lambda 表达式,很快我就在 Flink 的学习中用到了,我学的是 Java 版本的 Flink,一开始会以为代码会很复杂,但事实上 Flink 中很多地方都用到了 函数接口,这也让我们在编写 Flink 程序的时候可以使用 lambda 表达式非常地简洁地实现匿名函数。

        今天再来学习一个新的特性,Stream 流,光是看名字就觉得和大数据能扯上关系,我们的 Spark、Flink 当中不就都是这种流的概念嘛。

1、什么是 Strem 流

        Stream 是 JDK1.8 中处理集合的关键抽象概念, Lambda 表达式 和 Stream 是JDK1.8 新增的函数式编程中最有亮点的特性了,它可以指定你希望对集合进行操作,可以执行非常复杂的查询过滤和映射等操作。使用 Stream API 对集合数据进行操作,就类似于使用 SQL 来执行对 Java 集合运算和表达的高阶抽象。

        Stream API 可以极大地提高 Java 程序员的生产力,让程序员写出更加高效、干净、简洁的代码。那对我在大数据开发中更是如此。

        这种风格将要处理的元素集合看做一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如过滤、排序、聚合等。

2、Stream 创建方式

1、创建串行 Stream

Stream<User> userStream = list.stream();

2、创建并行 Stream

Stream<User> userStream = list.parallelStream();

3、关闭

在Java中,Stream只能被操作一次,一旦你对其进行了一次操作(比如forEach, collect等),它就会被关闭,再次操作就会报错:stream has already been operated upon or closed。

3、Stream 将 List 转换为 Set

1、创建 List 集合

Stream 是通过集合创建出来的,所以我们先创建一个集合,而集合内我们需要存放实体,所以先创建一个实体类 User:

public class User {public String name;public int age;public User(){}public User(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;User user = (User) o;return age == user.age && Objects.equals(name, user.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

创建集合

List<User> list = new ArrayList<>();list.add(new User("燕双鹰",28));list.add(new User("李大喜",20));list.add(new User("李元芳", 30));list.add(new User("李元芳", 30));

重写 equals

注意:这里我们对实体类的 equals 和 hashcode 方法进行了重写,这在之前我是不会去重写的。重写和不重写的区别就是:
        重写后,当两个实体对象的属性相同时,equals 方法返回 true,如果没有重写,则 equals 返回 false。

== 和 equals

        == 用于比较基本数据类型的值是否相等或者对象的引用地址是否相同。

int a = 10;
int b = 10;
System.out.println(a==b);    //trueString str1 = "hello";
String str2 = "hello";
System.out.println(str1==str2);    //false

equals 用于比较两个对象的内容是否相等。在Object类中,默认的“equals()”实现使用“==”操作符比较对象的引用。但是,许多类(如String、Integer等)重写了“equals()”方法,以便根据类的特定属性比较对象的内容。

set 去重底层原理

set 去重底层依赖于 map 集合实现放重复的 key,map 集合底层基于 equals ,它先比较 key 的hashcode 是否相同,相同情况下再调用 equals 方法判断是否真的相等。

所以一个实体类是否重写 equals 方法区别很大。

User u1 = new User("s",1);User u2 = new User("s",1);System.out.println(u1.equals(u2));

上面的代码,如果我们以 User 对象作为 key,如果我们的 User 没有重写 equals 方法,那么返回的就是 false,因为默认使用 == ,引用地址不同;如果重写了 equals 方法,那么返回的就是 true,因为使用重写后的 equals ,两个对象属性相同返回 true。

注意:对象的比较不会去比较 hashcode。 

HashMap<User, String> map = new HashMap<>();map.put(u1,"a");map.put(u2,"b");System.out.println(map.get(u1).equals(map.get(u2)));

上面的代码,如果我们没有重写 hashcode 的情况下,那么返回的就是 true,因为 map 的底层是通过 hashcode 来比较两个 key 是否相同;如果重写了 hashcode ,那么返回的就是 true。

2、List 转为 Set

public static void main(String[] args) {List<User> list = new ArrayList<>();list.add(new User("燕双鹰",28));list.add(new User("李大喜",20));// 下面是两个属性相同的两个对象(我们已经重写了 equals 和 hashcode 方法)list.add(new User("李元芳", 30));list.add(new User("李元芳", 30));// todo 创建 Stream 的两种方式// 1. 串行流 stream() 单线程Stream<User> stream = list.stream();Set<User> set = stream.collect(Collectors.toSet());set.forEach(user->{System.out.println(user.toString());});
}

运行结果:

User{name='李元芳', age=30}
User{name='燕双鹰', age=28}
User{name='李大喜', age=20}

可以看到,重写 equals 和 hashcode 方法后,虽然相同属性的两个对象的内存地址不同,但也被去除重复了。

4、Stream 将 List 转为 Map

1、创建 List

注意:List 转为 Map 的时候,由于 Map 集合不允许存在重复的 key,所以我们必须保证 list 集合中作为 key 字段的属性值唯一。

List<User> list = new ArrayList<>();list.add(new User("燕双鹰",28));list.add(new User("李大喜",20));list.add(new User("李元芳", 30));

 2、List 转为 Map

 Stream<User> stream = list.stream();// list 集合是没有 key 的,所以不能直接转为 map 集合,需要指定 key(指定对象的某个字段作为key)Map<String, User> collect = stream.collect(Collectors.toMap(new Function<User, String>() {  // 第一个参数 list中的类型,第二个参数是key类型: String@Overridepublic String apply(User user) {return user.getName();}}, new Function<User, User>() { // 第一个参数 list中的类型,第二个参数是value类型: User@Overridepublic User apply(User user) {return user;}}));collect.forEach(new BiConsumer<String, User>() {@Overridepublic void accept(String key, User user) {System.out.println(key+","+user.toString());}}); 

使用 lambda 表达式简化一下代码:

        // 用lambda表达式Map<String, User> collect = stream.collect(Collectors.toMap(User::getName, user -> user));collect.forEach((key,user)-> System.out.println(key+","+user.toString()));

运行结果:

李元芳,User{name='李元芳', age=30}
李大喜,User{name='李大喜', age=20}
燕双鹰,User{name='燕双鹰', age=28}

5、Strem 通过 reduce 方法求和

1、简单求和

这里我们通过 Stream.of() 方法来进行数据的构造(这让我想到了最近 Flink)。

Stream<Integer> stream = Stream.of(10, 50, 30, 10);Optional<Integer> res = stream.reduce(new BinaryOperator<Integer>() {@Overridepublic Integer apply(Integer integer, Integer integer2) {return integer + integer2;}});

使用 lamda 表达式 

 Optional<Integer> res = stream.reduce(Integer::sum);

关于结果的打印,我们后面讲到 Optional 类的时候再详细说,一般直接:

System.out.println(res.get());

2、对象属性和

我们构造一个 List 集合,然后转为 Stream 调用 reduce 方法进行求和。

注意:reduce 方法的返回结果类型必须和 Stream 的类型一致(就像我们 Hadoop 中的 WordCount)。

List<User> list = new ArrayList<>();list.add(new User("燕双鹰",28));list.add(new User("李大喜",20));list.add(new User("李元芳", 30));Stream<User> stream = list.stream();Optional<User> sum = stream.reduce(new BinaryOperator<User>() {@Overridepublic User apply(User user1, User user2) {return new User("sum",user1.getAge()+ user2.getAge());}});System.out.println(sum.get());

lambda 表达式简化:

        Stream<User> stream = list.stream();Optional<User> sum = stream.reduce(((user1, user2) -> new User("sum", user1.getAge() + user2.getAge())));System.out.println(sum.get());//78

6、Strem 查找集合最大值和最小值

1、创建集合

List<User> list = new ArrayList<>();
list.add(new User("燕双鹰",28));
list.add(new User("李大喜",20));
list.add(new User("李元芳", 30));

2、查找最大 age 属性对象

Optional<User> max = stream.max(new Comparator<User>() {@Overridepublic int compare(User o1, User o2) {return o1.getAge() - o2.getAge();}});System.out.println(max.get());

lambda表达式简化:

Optional<User> max = stream.max((user1, user2) -> user1.getAge() - user2.getAge());System.out.println(max.get());    //30

3、查找最小 age 属性对象

Optional<User> min = stream.min((user1, user2) -> user1.getAge() - user2.getAge());System.out.println(min.get());    //20

7、Stream 中 Match 用法

anyMatch 表示,任意一个元素满足条件返回 true。

allMatch 表示,所有元素满足条件才会返回 true。

noMatch 表示,所有条件都不满足这个条件才会返回 true。

1、创建集合

List<User> list = new ArrayList<>();list.add(new User("燕双鹰",28));list.add(new User("李大喜",20));list.add(new User("李元芳", 30));Stream<User> stream = list.stream();

2、anyMatch

判断集合中是否存在 age 属性大于 25 的对象。

 boolean res = stream.anyMatch(new Predicate<User>() {@Overridepublic boolean test(User user) {return user.getAge() > 25;}});System.out.println(res);

 lambda 表达式:

boolean res = stream.anyMatch(user -> user.getAge() > 25);System.out.println(res);    //true

3、allMatch

判断是否所有对象的 age属性都大于 30

boolean res = stream.allMatch(user -> user.getAge() > 30);System.out.println(res);    //false

4、noMatch

判断是否用户都不满足 name 为 “光头强 ”

boolean res = stream.noneMatch(user -> user.getName().equals("光头强"));System.out.println(res); //true

8、Stream 过滤器

和我们 Flink 的 DataStream API 中的转换算子 filter 很像,它们都是把 判断条件结果为 true 的数据留下,false 则丢掉。

1、创建集合

List<User> list = new ArrayList<>();list.add(new User("燕双鹰",28));list.add(new User("李大喜",20));list.add(new User("李元芳", 30));Stream<User> stream = list.stream();

2、过滤

留下 年龄>25 岁的 User 对象。
Stream<User> filterStream = stream.filter(new Predicate<User>() {@Overridepublic boolean test(User user) {    //为 true 则留下return user.getAge()>25;}});filterStream.forEach(new Consumer<User>() {@Overridepublic void accept(User user) {System.out.println(user);}});

运行结果: 

User{name='燕双鹰', age=28}
User{name='李元芳', age=30}

lambda表达式:

Stream<User> filterStream = stream.filter(user -> user.getAge() > 25);filterStream.forEach(System.out::println);

9、Stream Limit 和 Skip

同样,Stream 需要通过集合来创建。

List<User> list = new ArrayList<>();
list.add(new User("燕双鹰",28));
list.add(new User("李大喜",20));
list.add(new User("李元芳", 30));
list.add(new User("熊大",15));
list.add(new User("熊二",14));
list.add(new User("光头强",20));
Stream<User> stream = list.stream();

1、取出前2条数据

// 在mysql中limit(start,end)需要传两个参数,但在这里只允许传入一个long类型的 maxSize// 取前2条数据stream.limit(2).forEach(System.out::println);

运行结果:

User{name='燕双鹰', age=28}
User{name='李大喜', age=20}

 

2、取出第 [3,6) 条数据

注意,这里的索引是从 0 开始的。

// 取 [3,6)条数据 想要分页从先 skip 再 limit
stream.skip(2).limit(3).forEach(System.out::println);

运行结果:
 

User{name='李元芳', age=30}
User{name='熊大', age=15}
User{name='熊二', age=14}

10、Stream 排序 sorted

下面用到的数据。

List<User> list = new ArrayList<>();
list.add(new User("燕双鹰",28));
list.add(new User("李大喜",20));
list.add(new User("李元芳", 30));
list.add(new User("熊大",15));
list.add(new User("熊二",14));
list.add(new User("光头强",20));
Stream<User> stream = list.stream();

1、直接排序

对于数值型的数据可以直接进行排序

Stream<Integer> integerStream = Stream.of(1, 5, 8, 3, 7);
integerStream.sorted().forEach(System.out::println);    //1 3 5 7 8

2、根据对象字段进行升序

stream.sorted(new Comparator<User>() {@Overridepublic int compare(User o1, User o2) {return o1.getAge()-o2.getAge();}
}).forEach(System.out::println);

lambda 表达式:

stream.sorted((o1, o2) -> o1.getAge()-o2.getAge()).forEach(System.out::println);

运行结果: 

User{name='熊二', age=14}
User{name='熊大', age=15}
User{name='李大喜', age=20}
User{name='光头强', age=20}
User{name='燕双鹰', age=28}
User{name='李元芳', age=30}

JDK1.8 提供的函数接口

都在包 java.util.function 包下。

并行流

案例1 - 500亿次求和

1、使用单线程

Instant start = Instant.now();long sum = 0;for (long i = 0; i <= 50000000000L; i++) {sum+=i;}Instant end = Instant.now();System.out.println(sum);System.out.println("500亿次求和花费时间: "+ Duration.between(start,end).toMillis()+"ms");   // 单线程 11s左右  多线程 6s左右

2、使用并行流

Instant start = Instant.now();LongStream longStream = LongStream.rangeClosed(0,50000000000L);OptionalLong result = longStream.parallel().reduce(new LongBinaryOperator() {@Overridepublic long applyAsLong(long left, long right) {return left + right;}});Instant end = Instant.now();System.out.println(result.getAsLong());System.out.println("500亿次求和花费时间: "+ Duration.between(start,end).toMillis()+"ms");   // 单线程 11s左右  多线程 6s左右

可以发现,多线程明显要快很多。

总结

        本次学习收获非常大,函数接口的思想在 Flink 中随处可见,的确,这样一种能够使得代码简洁高效的技术在大数据开发中是非常重要的。

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

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

相关文章

MIB 6.1810实验Xv6 and Unix utilities(2)sleep

难度:easy Implement a user-level sleep program for xv6, along the lines of the UNIX sleep command. Your sleep should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts f…

2D 3D 工业组态技术 meta2d JavaScript

本心、输入输出、结果 文章目录 2D 3D 工业组态技术 meta2d JavaScript前言2D 3D 工业组态技术 meta2d JavaScript 简介2D 3D 工业组态技术 meta2d JavaScript 特性丰富的组态能力0代码数据通信组态的应用多端适配能力强大的扩展能力追求卓越性能丰富的组件库资源广泛的应用场景…

C语言变量与常量

跟着肯哥&#xff08;不是我&#xff09;学C语言的变量和常量、跨文件访问、栈空间 栈空间还不清楚&#xff0c;期待明天的课程内容 C变量 变量&#xff08;Variable&#xff09;是用于存储和表示数据值的名称。 主要包括四个环节&#xff1a;定义、初始化、声明、使用 在我刚…

什么是BT种子!磁力链接又是如何工作的?

目录 一.什么是BT&#xff1f;1.BT简介&#xff1a;1.1.BT是目前最热门的下载方式之一1.2.BT服务器是通过一种传销的方式来实现文件共享的 2.小知识&#xff1a;2.1.你知道吗BT下载和常规下载到底有哪些不同2.2.BT下载的灵魂&#xff1a;种子2.3.当下载结束后&#xff0c;如果未…

mysql客户端navicat的一些错误合集

关于mysql的客户端的使用的一些问题 问题描述&#xff1a; 在使用navicat prenium客户端的时候&#xff0c;连接数据库出现 Table ‘performance_schema.session_variables’ doesn’t exist 错误 解决方案&#xff1a; 首先找到mysql的bin目录 然后winR 进入到cmd界面 输入…

《循环双向链表》(带哨兵位的头节点)

目录 ​编辑 前言&#xff1a; 关于双向循环带头链表: 模拟实现双向循环带头链表&#xff1a; 1.typedef数据类型 2.打印链表 3.初始化链表&#xff1a; 4.创建节点 5.尾插 6.头插 7.尾删 8.头删 9.寻找节点 10.在节点前插入 11.删除指定节点 单链表和双链表的区别…

解析:什么是生成式AI?与其他类型的AI有何不同?

原创 | 文 BFT机器人 快速浏览一下头条新闻&#xff0c;你会发现生成式AI似乎无处不在。事实上&#xff0c;一些新闻标题甚至可能是通过生成式AI编写的&#xff0c;例如OpenAI旗下的ChatGPT&#xff0c;这个聊天机器人已经展现出了生成看起来像人类所写文本的惊人能力。 当人们…

maptalks三维地图网址

三维 地址: http://examples.maptalks.com/examples/cn/gltf/gltf-marker/shader

简朴博客系统测试报告

文章目录 一. 项目简介二. 测试概要三. 测试环境四. 测试执行概况及功能测试1. 手工测试1.1 手动测试用例编写1.2 执行的部分测试用例 2. 自动化测试Selenium2.1 编写测试用例2.2 自动化测试代码 3. 测试结果 五. 发现的问题 一. 项目简介 简朴博客系统是采用前后端分离的方式…

asp.net core mvc 之 依赖注入

一、视图中使用依赖注入 1、core目录下添加 LogHelperService.cs 类 public class LogHelperService{public void Add(){}public string Read(){return "日志读取";}} 2、Startup.cs 文件中 注入依赖注入 3、Views目录中 _ViewImports.cshtml 添加引用 4、视图使用…

Python每日一练@前言

Python每日一练前言 导读 人生苦短&#xff0c;我用Python 大家好&#xff0c;我是鹅不糊涂 欢迎大家来到Python每日一练 好处 加强编程能力: 每日一练可以帮助提升编程技能&#xff0c;通过解决各种编程问题和挑战&#xff0c;你能够不断锻炼自己的逻辑思维和解决问题的能力…

顺序表(数据结构与算法)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

2023.11.17 hadoop之HDFS进阶

目录 HDFS的机制 元数据简介 元数据存储流程:namenode 生成了多个edits文件和一个fsimage文件 edits和fsimage文件 SecondaryNameNode辅助NameNode的方式: HDFS的存储原理 写入数据原理: 发送写入请求,获取主节点同意,开始写入,写入完成 读取数据原理:发送读取请求,获取…

vim——“Linux”

各位CSDN的uu们好呀&#xff0c;今天&#xff0c;小雅兰的内容是Linux的开发工具——vim。下面&#xff0c;我们一起进入Linux的世界吧&#xff01;&#xff01;&#xff01; Linux编辑器-vim使用 vim的基本概念 vim的基本操作 vim正常模式命令集 vim末行模式命令集 vim操…

【Linux网络】从原理到实操,感受PXE无人值守自动化高效批量网络安装系统

一、PXE网络批量装机的介绍 1、常见的三种系统安装方式 2、回顾系统安装的过程&#xff0c;了解系统安装的必要条件 3、什么是pxe 4、搭建pxe的原理 5、Linux的光盘镜像中的isolinux中的相关文件学习 二、关于实现PXE无人值守装机的四大文件与五个软件的对应关系详解 5个…

使用 Redis BitMap 实现签到与查询历史签到以及签到统计功能(SpringBoot环境)

目录 一、前言二、Redis BitMap 位图原理2.1、BitMap 能解决什么2.2、BitMap 存储空间计算2.3、BitMap 存在问题 三、Redis BitMap 操作基本语法和原生实现签到3.1、基本语法3.2、Redis BitMap 实现签到操作指令 四、SpringBoot 使用 Redis BitMap 实现签到与统计功能4.1、代码…

python之 flask 框架(2)项目拆分的 执行逻辑

项目的结构图 app.py # 导入__init__.py 比较特殊 from APP import create_appapp create_app() if __name__ __main__:app.run(debugTrue)init.py # __inti__.py # 初始化文件&#xff0c;创建Flask应用 from flask import Flask from .views import bluedef create_ap…

PyTorch 实战之水果分类

当我们试图提高神经网络的准确性时&#xff0c;经常会遇到过拟合训练数据的情况。当我们运行测试数据的模型时&#xff0c;这会导致一个糟糕的预测。因此&#xff0c;我采取了一个数据集&#xff0c;并应用这些技术&#xff0c;不仅提高准确性&#xff0c;而且还处理过拟合的问…

基于IDEA 进行Maven依赖管理

1. 依赖管理概念 Maven 依赖管理是 Maven 软件中最重要的功能之一。Maven 的依赖管理能够帮助开发人员自动解决软件包依赖问题&#xff0c;使得开发人员能够轻松地将其他开发人员开发的模块或第三方框架集成到自己的应用程序或模块中&#xff0c;避免出现版本冲突和依赖缺失等…

一个用于操作Excel文件的.NET开源库

推荐一个高性能、跨平台的操作Excel文件的.NET开源库。 01 项目简介 ClosedXML是一个.NET第三方开源库&#xff0c;支持读取、操作和写入Excel 2007 (.xlsx&#xff0c; .xlsm)文件&#xff0c;是基于OpenXML封装的&#xff0c;让开发人员无需了解OpenXML API底层API&#xf…