1.Java Stream流与Optional流浅析
2.解析Stream foreach源码
3.于——InputStream类源码详解
4.FFmpeg源码分析: AVStream码流
5.使用JDK8 Stream空指针问题分析
6.从原理剖析带你理解Stream
Java Stream流与Optional流浅析
Stream流
1. 操作类型
Stream API中的源码操作类型主要分为两大类:中间操作和终止操作。中间操作仅作为标记,源码实际计算会在触发终止操作时进行。源码
2. Stream的源码操作过程
首先,我们准备了一些示例代码。源码在TestStream类中,源码网墓app源码我们定义了一些测试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()方法。
在该方法中,ue源码模式创建了一个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对象,并且将参数中的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. 代码建议
解析Stream foreach源码
本文深入解析Stream的foreach操作源码,主要关注串行流和并行流的区别,特别是并行流背后的ForkJoin框架。 在Stream中,操作可分为中间操作和结束操作,其中foreach属于结束操作。串行流与并行流的主要区别在于实现方式,串行流是线性执行,而并行流则利用了ForkJoin框架的分治策略。 对于串行流(如`stream`),其执行过程如下:获取ReferencePipeline.Head的Stream实现,内部包含ArrayListSpliterator对象。
通过ArrayListSpliterator的forEachRemaining方法逐一执行元素操作。
而并行流(如`parallelStream`)则更为复杂:同样获取ReferencePipeline.Head的Stream实现,内部有ArrayListSpliterator。
调用父类的forEach方法,构建一个ForEachTask。
在ForEachTask的invoke方法中,调用compute方法,利用ForkJoin框架的分治策略将任务拆分到commonPool中的线程池执行。
子任务通过拆分器的forEachRemaining方法,最终执行用户定义的action.accept(e)回调。
ForkJoin框架是JDK7新增的,它通过线程池执行任务,尤其适用于并行处理。在并行流中,任务会分配到Java 8中预定义的biasql指标源码commonPool,该线程池基于计算机处理器数量进行配置,以实现高效的并行计算。于——InputStream类源码详解
InputStream类是字节输入流的基础,它作为所有字节输入流类的超类,提供了读取字节的基本功能。
从InputStream读取下一个数据字节时,返回的值位于0到的整数范围内,代表字节值。若流已到达末尾而无更多字节,会返回值-1。在获取数据、遇到流终点或抛出异常之前,此方法始终处于阻塞状态。
实现InputStream接口的子类通常会根据具体的应用场景,扩展或修改InputStream的基础行为。例如,FileInputStream用于从文件读取字节,而ByteArrayInputStream用于从字节数组读取。
InputStream类提供了基础的读取操作,包括read()方法用于读取单个字节,read(byte[] b)方法用于读取多个字节到字节数组中,以及read(byte[] b, int off, int len)方法用于指定读取字节的位置和数量。这些方法共同构成InputStream类的核心功能。
通过使用InputStream类及其子类,开发者可以实现从文件、网络连接、设备输入或其他数据源的字节读取,为数据处理、文件操作和网络通信等提供了基础支持。
在实际应用中,开发者需谨慎处理异常情况,比如文件未找到、网络连接断开或读取操作超时等,并合理使用非阻塞读取机制,以提高程序的性能和响应速度。
总之,InputStream类作为字节输入流的基础,为各种应用场景提供了灵活和高效的数据读取能力。深入理解其内部机制和用法,对于开发高效、可靠的软件系统至关重要。
FFmpeg源码分析: AVStream码流
在AVCodecContext结构体中,AVStream数组存储着所有视频、音频和字幕流的信息。每个码流包含时间基、时长、索引数组、编解码器参数、dts和元数据。索引数组用于保存帧数据包的offset、size、timestamp和flag,方便进行seek定位。
让我们通过ffprobe查看mp4文件的码流信息。该文件包含5个码流,是双音轨双字幕文件。第一个是video,编码为h,帧率为.fps,分辨率为x,像素格式为yuvp。第二个和第三个都是audio,编码为aac,采样率为,立体声,语言分别为印地语和英语。第四个和第五个都是subtitle,语言为英语,编码器为mov_text和mov_text。
调试实时数据显示,stream数组包含以下信息:codec_type(媒体类型)、codec_id、bit_rate、profile、level、width、height、sample_rate、channels等编解码器参数。
我们关注AVCodecContext的编解码器参数,例如codec_type、codec_id、bit_rate、profile、level、width、height、sample_rate和channels。具体参数如下:codec_type - 视频/音频/字幕;codec_id - 编码器ID;bit_rate - 位率;profile - 编码器配置文件;level - 编码器级别;width - 宽度;height - 高度;sample_rate - 采样率;channels - 音道数。
AVStream内部的nb_index_entries(索引数组长度)和index_entries(索引数组)记录着offset、size、timestamp、flags和min_distance信息。在seek操作中,通过二分查找timestamp数组来定位指定时间戳对应的帧。seek模式有previous、next、nearest,通常使用previous模式向前查找。
时间基time_base在ffmpeg中用于计算时间戳。在rational.h中,AVRational结构体定义为一个有理数,用于时间计算。要将时间戳转换为真实时间,只需将num分子除以den分母。
使用JDK8 Stream空指针问题分析
在使用JDK8 Stream API进行集合操作转换时,虽然它极大地方便了代码编写,但也容易引发一些问题。本文将总结一些我们在开发中遇到的常见问题以及解决策略。
首先,让我们来关注Collectors.toList 存在null值的问题。在处理Stream流时,可能不经意间将null值添加至List对象中。尽管List允许null值存在,但在进行循环遍历时,null值可能导致空指针异常。为了避免这种情况,建议在使用Collectors.toList前,先通过filter方法剔除null值。
紧接着,我们来看看Collectors.toMap出现NullPointerException的情况。通常,HashMap允许key和value为null,但在Stream API处理时,如果value为null,则会触发空指针异常。分析源码,发现在合并操作中,如果value为null,会抛出空指针异常。为避免此问题,可以在调用toMap之前,使用filter方法去除可能存在的null值。
另外,使用Collectors.toMap时,还需注意可能出现的Duplicate key问题。虽然源码中的处理逻辑在检测到重复key时会抛出异常,但我们可以自定义mergeFunction参数,以便在处理重复key时实现特定逻辑,如取第一个value或最后一个value,以此覆盖或保留前一个值。
在使用parallelStream时,重要的一点是它不保证集合顺序。这意味着,当使用parallelStream进行并行执行时,结果的顺序可能与预期不同。若需要保持顺序,可以调用parallelStream().forEachOrdered()方法。
此外,parallelStream还可能引发线程安全问题。在并行执行时,多线程并发操作可能导致数据不一致。处理这一问题,一种方法是确保parallelStream().forEach()逻辑内的线程安全,另一种方法是将集合转换为并发集合,如使用ConcurrentHashMap或使用Guava库中的并发集合。
通过上述分析,我们可以更好地理解和管理使用JDK8 Stream API时可能出现的问题,确保代码的稳定性和可靠性。
从原理剖析带你理解Stream
Stream是Java 8提供的新特性,它允许我们以声明式的方式处理数据集合,简化了集合操作的代码结构。在项目中,集合是最常用的数据存储结构,当我们需要对集合内的元素进行过滤或其他操作时,传统的做法是使用for循环。Stream操作分为中间操作与结束操作两大类。中间操作仅进行记录,直到结束操作才会触发实际计算,这种特性称为懒加载,使得Stream在处理大规模对象迭代计算时非常高效。中间操作又分为有状态与无状态操作,有状态操作需要在处理所有元素后才能进行,无状态操作则不受之前元素的影响。
Stream结构分析揭示了其内部实现机制。每一次中间操作都会生成新的Stream对象,无状态操作的实现类为StatelessOp,有状态操作的实现类为StatefulOp。通过继承关系,我们可以观察到Stream结构的层次性。核心Sink概念在Stream API内部实现中扮演关键角色,Stream API通过重载Sink的接口方法实现了其功能。以filter或map方法为例,源码返回的StatelessOp或StatefulOp对象构成了一个复杂的结构,最终与Sink相关联。Sink对象在Stream执行流程中扮演关键角色,其作用在collect方法中得以体现,通过匿名内部类ReducingSink对象实现元素的收集与处理。动画理解Stream执行流程可以帮助我们更直观地了解其运行机制,从而深入掌握其高效处理数据集合的方法。
node stream源码分析 — Readable
Stream在Node.js中是一种数据传输的抽象机制,它分为四种类型:流、可读流(Readable)、可写流(Writable)和可缓冲流(Transform)。其中,可读流(Readable)用于从外部数据源读取数据。
可读流有两种模式:流动模式和非流动模式。非流动模式在监听到'data'事件时,直接读取数据而不暂停,并不将数据存储到缓存区。流动模式则在监听到'readable'事件时,将数据放入缓存区,并等待'writable'调用来判断是否有空位,以此来决定是否暂停。
以下是对可读流(Readable)的源码分析。首先,让我们查看Readable的源码。源码文件位于'_stream_readable.js'中。
在'fs.js'文件中,我们可以看到创建读取流的源码,而'Readable'则位于'_stream_readable.js'文件中。
在'fs.js'文件中,我们可以通过调用`fs.createReadStream`来创建读取流。在'Readable'源码文件中,我们可以看到Node.js实现的可读流类,它提供了读取数据的功能,并且支持缓冲和流式读取。