1.Java Stream流与Optional流浅析
2.Java 8 开始新增的源码 Optional 类 - 检查 Optional 的值
3.Java8新特性-Optional类
4.Java8之Optional中orElse和orElseGet的区别
5.python str.find()底层用的什么算法
6.使用protobuf实现序列化与反序列化
Java Stream流与Optional流浅析
Stream流
1. 操作类型
Stream API中的操作类型主要分为两大类:中间操作和终止操作。中间操作仅作为标记,源码实际计算会在触发终止操作时进行。源码
2. Stream的源码操作过程
首先,我们准备了一些示例代码。源码在TestStream类中,源码聊天 系统 源码我们定义了一些测试lambda函数的源码方法。在main方法中,源码我们执行了一个相关的源码流操作,在控制台中并没有看到任何输出。源码这说明Stream并没有真正执行到对应的源码方法中,因为我们没有写入终止操作。源码由此可见,源码在终止操作之前,源码Stream并没有真正去执行每个中间操作,源码而是将中间操作记录了下来。在执行终止操作这一行代码时,再去执行中间操作。
2.1 记录过程
进入源码后,可以看到Collection的Stream方法调用了StreamSupport.stream()方法。在该方法中,返回了一个ReferencePipeline.Head对象,这是记录管道操作的头节点对象。这个Head对象继承了ReferencePipeline对象,所以后续的map、filter等方法实际上是ReferencePipeline对象的方法。在构造方法中,也调用了父类AbstractPipeline类的构造方法。
在Stream中,每一步操作都被定义为一个Stage。在构造方法中,定义了previousStage和sourceStage,即上一个节点和头节点。开源的小程序商城源码在类中还有一个nextStage对象。
Stream实际上构建了一个双向链表来记录每一步操作。接下来,我们看一下list.map()方法。
在该方法中,创建了一个StatelessOp对象,它代表无状态的中间操作。这个对象同样继承了ReferencePipeline。在该对象的构造方法中,将调用该初始化方法的节点定义为上一个节点,并且对应的深度depth也进行了+1操作。
我们总结一下,stream()方法得到的是HeadStage,之后每一个操作(Operation)都会创建一个新的Stage,并以双向链表的形式结合在一起。每个Stage都记录了本身的操作。Stream就以此方式实现了对操作的记录。注意,结束操作不算depth的深度,它也不属于stage。但是我们的示例语句中没有写结束操作的代码,所以在这里提一下Stream的Lazy机制。它的特点是:Stream直到调用终止操作时才会开始计算,没有终止操作的Stream将是一个静默的无操作指令。
Stage相关类如下
2.2 执行过程
在了解执行过程之前,我们应该先了解另一个接口Sink,它继承了Consumer接口。在调用map、filter等无状态操作中返回的StatelessOp对象中,覆盖了opWrapSink方法,返回了一个Sink对象,并且将参数中的H5拉霸源码Sink对象作为构造方法中的参数传入进去。
走进构造方法后,可以看到在该对象中定义了一个downstream,该对象也是一个Sink类型的对象,并且在定义Sink对象时,覆盖了Consumer接口中的accept方法。
不难看出,在执行accept方法时,就是将当前节点的操作结果传入给downstream继续执行,而这个downstream则是通过onWrapSink方法中传入过来的。
了解了以上这些概念,我们可以走进结束操作.collect(Collectors.toList());方法。在该方法中,通过Collectors定义了一个另一个ArrayList收集器,并且传入了collect方法中。
我们暂时只看非并行的部分。在这一行通过ReduceOps定义了一个ReduceOp对象。
在makeRef方法中,返回了一个ReduceOp对象,该对象覆盖了makeSink()方法,返回了一个ReducingSink对象。我们继续往下走,走进evaluate方法中。
可以看出,wrapsink方法中,是查找链表的头节点,并且调用每个节点的onWrapSink方法,在该方法中传入当前节点的sink对象,并且将传入的对象定义成自己的下游,形成一个从头节点到尾部节点的Sink单向链表。
在wrapSink中,通过一层层的前置包装,返回头节点的博乐达三七精华源码Sink类传入copyInto方法中。
在该方法中,先调用了wrappedSink.begin()方法,该方法默认实现为调用downstream的begin方法。相当于触发全部Sink的begin方法,做好运行前的准备。
具体循环的执行则是在spliterator.forEachRemaining(wrappedSink);方法中,操作如下
在forEachRemaining方法中,调用了accept方法,也就是在定义onWrapSink方法中初始化Sink对象后定义的accept方法,将自己的执行结果传入downstream继续执行,也就是说,在调用结束操作后才实际执行每个方法。在实际执行过后,在执行end方法进行结束操作。Stream整体的流操作大概就是如此。了解了大概过程后可以找一些常用的case来分析一下。
2.3 具体分析
一般情况下都会选择list作为排序容器,大部分情况下都是不知道容器大小的,于是采用RefSortingSink类作为当前节点处理类,该类代码如下。
可以看到该Sink中的accept方法中,并没有执行下游的accept方法,而是将所有的数据装入了一个ArrayList,在end方法利用arrayList进行排序,并且继续开启后续的循环操作。
3. 代码建议
Java 8 开始新增的 Optional 类 - 检查 Optional 的值
在Java 8引入的Optional类中,isPresent()方法用于检查Optional对象中是否存在值。无论是自定义创建的Optional对象,还是从其他方法返回的Optional对象,我们都能使用此方法。如果Optional对象内的值非null,isPresent()方法将返回真值。中易v9源码
而Java 中,我们则可以使用与isPresent相反的方法,即isEmpty()。当Optional对象中的值为null时,isEmpty方法将返回真值。
在JDK源代码中,这两个方法的实现都简单地通过判断是否等于null来完成。isPresent()和isEmpty()方法的使用原则相反。在实际应用中,我们可根据需求选择合适的检查方法。
Java8新特性-Optional类
在Java应用开发中,避免NPE问题一直是开发者面临的一大挑战。Guava项目通过引入Optional类,为解决这一问题提供了全新的思路。Optional类作为Java 8的一部分,旨在优雅地解决NPE问题,促进代码简洁性和可读性。
Optional类是Java中用于表示可能不存在的值的容器类,它用`value`变量存储实际值,或仅存储`null`,以表示值不存在。相比使用`null`来表示无值状态,Optional更精确地描述了值的有无,有效避免了空指针异常,并鼓励了函数式编程风格的实现。
基本使用示例展示了如何获取用户所在地方的编号。引入Optional后,只需在最后执行一次空值判断,极大简化了代码结构,同时提供了`orElse`、`orElseGet`、`orElseThrow`等方法,为处理空值提供了灵活的解决方案。调用Optional的`toString()`方法时,若值为空,则返回`"Optional.empty"`,避免了直接抛出空指针异常。
Optional类提供了丰富的API以进行数据操作。通过`map`、`filter`、`flatMap`等方法,开发者可以对包装对象进行转换和过滤,确保操作的安全性。这些方法在处理值存在性的同时,保持了代码的简洁性和功能性。
获取值时,Optional提供了多种方法,每种方法依据需求不同而设计,确保了在确保代码安全的同时,提供了灵活的访问方式。
深入Optional的源码分析,探究了构造方法、实例方法、空值判断、数据处理和数据获取等关键部分,展示了Optional如何在内部结构和功能上实现其独特设计,从而在Java生态系统中扮演了关键角色。通过其高效的API和清晰的设计,Optional类不仅简化了代码实现,还提升了开发者的编程体验,是现代Java应用开发中不可或缺的工具。
Java8之Optional中orElse和orElseGet的区别
在探讨Java8的Optional类中orElse和orElseGet的区别时,许多文章常会提出类似的疑问,例如以下例子所示:
初见此场景,可能感到疑惑:明明已有值,为何还要执行?这似乎违背了orElse的初衷。带着疑问,我们深入查看了orElse的源码。
初时,对于传入类调用与接收对象间的关联,感到困惑,直到豁然开朗:在执行orElse之前,参数值的获取是必不可少的。因此,执行传入的方法是必须的步骤。
实际跟踪代码,我们可以发现,执行orElse之前,已调用了getDefault方法。进一步对比orElseGet的源码,更清晰地理解了两者间的关键差异。
通过思考,我渐渐领悟到了orElse和orElseGet语义的本质区别。
python str.find()底层用的什么算法
1、python 下面的str是一个类,里面包含各种方法,其中之一就是find()
源码如下,这个类如果全部放上来,会超过最大字数限制,里面解释内容太多了
def find(self, sub, start=None, end=None): # real signature unknown; restored from __doc__
"""
B.find(sub[, start[, end]]) -> int
Return the lowest index in B where subsection sub is found,
such that sub is contained within B[start,end]. Optional
arguments start and end are interpreted as in slice notation.
Return -1 on failure.
"""
return 0
使用protobuf实现序列化与反序列化
protobuf是用来干嘛的?
protobuf是一种用于对结构数据进行序列化的工具,从而实现数据存储和交换。(主要用于网络通信中收发两端进行消息交互。所谓的“结构数据”是指类似于struct结构体的数据,可用于表示一个网络消息。当结构体中存在函数指针类型时,直接对其存储或传输相当于是“浅拷贝”,而对其序列化后则是“深拷贝”。)
序列化:将结构数据或者对象转换成能够用于存储和传输的格式。 反序列化:在其他的计算环境中,将序列化后的数据还原为数据结构和对象。
从“序列化”字面上的理解,似乎使用C语言中的struct结构体就可以实现序列化的功能:将结构数据填充到定义好的结构体中的对应字段即可,接收方再对结构体进行解析。
在单机的不同进程间通信时,使用struct结构体这种方法实现“序列化”和“反序列化”的功能问题不大,但是,在网络编程中,即面向网络中不同主机间的通信时,则不能使用struct结构体,原因在于:
(1)跨语言平台,例如发送方是用C语言编写的程序,接收方是用Java语言编写的程序,不同语言的struct结构体定义方式不同,不能直接解析;
(2)struct结构体存在内存对齐和CPU不兼容的问题。
因此,在网络编程中,实现“序列化”和“反序列化”功能需要使用通用的组件,如 Json、XML、protobuf 等。
① 性能高效: 与XML相比,protobuf更小(3 ~ 倍)、更快( ~ 倍)、更为简单。
② 语言无关、平台无关: protobuf支持Java、C++、Python等多种语言,支持多个平台。
③ 扩展性、兼容性强: 只需要使用protobuf对结构数据进行一次描述,即可从各种数据流中读取结构数据,更新数据结构时不会破坏原有的程序。
Protobuf与XML、Json的性能对比:
测试万次序列化:
测试万次反序列化:
protobuf 2 中有三种数据类型限定修饰符:
required表示字段必选,optional表示字段可选,repeated表示一个数组类型。
其中, required 和 optional 已在 proto3 弃用了。
protobuf中常用的数据类型:
下载protobuf压缩包后,解压、配置、编译、安装,即可使用protoc命令查看Linux中是否安装成功:
使用protobuf时,需要先根据应用需求编写 .proto 文件定义消息体格式,例如:
其中,syntax关键字表示使用的protobuf的版本,如不指定则默认使用 "proto2";package关键字表示“包”,生成目标语言文件后对应C++中的namespace命名空间,用于防止不同的消息类型间的命名冲突。
然后使用 protobuf编译器(protoc命令)将编写好的 .proto 文件生成目标语言文件(例如目标语言是C++,则会生成 .cc 和 .h 文件),例如:
其中:
$SRC_DIR表示 .proto文件所在的源目录; $DST_DIR表示生成目标语言代码的目标目录; xxx.proto表示要对哪个.proto文件进行解析; --cpp_out表示生成C++代码。
编译完成后,将会在目标目录中生成xxx.pb.h和xxx.pb.cc文件,将其引入到我们的C++工程中即可实现使用protobuf进行序列化:
在C++源文件中包含xxx.pb.h头文件,在g++编译时链接xxx.pb.cc源文件即可:
在protobuf源码中的/examples 目录下有官方提供的protobuf使用示例:addressbook.proto
参考官方示例实现C++使用protobuf进行序列化和反序列化:
addressbook.proto :生成的addressbook.pb.h 文件内容摘要:add_person.cpp :
输出结果:
三种序列化的方法没有本质上的区别,只是序列化后输出的格式不同,可以供不同的应用场景使用。 序列化的API函数均为const成员函数,因为序列化不会改变类对象的内容,而是将序列化的结果保存到函数入参指定的地址中。
.proto文件中的option选项用于配置protobuf编译后生成目标语言文件中的代码量,可设置为SPEED, CODE_SIZE, LITE_RUNTIME 三种。 默认option选项为 SPEED,常用的选项为 LITE_RUNTIME。
三者的区别在于:
① SPEED(默认值): 表示生成的代码运行效率高,但是由此生成的代码编译后会占用更多的空间。
② CODE_SIZE: 与SPEED恰恰相反,代码运行效率较低,但是由此生成的代码编译后会占用更少的空间,通常用于资源有限的平台,如Mobile。
③ LITE_RUNTIME: 生成的代码执行效率高,同时生成代码编译后的所占用的空间也非常少。 这是以牺牲Protobuf提供的反射功能为代价的。 因此我们在C++中链接Protobuf库时仅需链接libprotobuf-lite,而非protobuf。
SPEED 和 LITE_RUNTIME相比,在于调试级别上,例如 msg.SerializeToString(&str;); 在 SPEED 模式下会利用反射机制打印出详细字段和字段值,但是 LITE_RUNTIME 则仅仅打印字段值组成的字符串。
因此:可以在调试阶段使用 SPEED 模式,而上线以后提升性能使用 LITE_RUNTIME 模式优化。
最直观的区别是使用三种不同的 option 选项时,编译后产生的 .pb.h 中自定义的类所继承的 protobuf类不同:
① protobuf 将消息里的每个字段进行编码后,再利用TLV或者TV的方式进行数据存储; ② protobuf 对于不同类型的数据会使用不同的编码和存储方式; ③ protobuf 的编码和存储方式是其性能优越、数据体积小的原因。
2024-12-29 17:04
2024-12-29 16:29
2024-12-29 16:12
2024-12-29 15:52
2024-12-29 15:45
2024-12-29 15:22