当前位置: 首页 > news >正文

Kotlin 中的惰性集合

1 通过序列提高效率

首先看以下代码:

val list = listOf(1, 2, 3, 4, 5)
list.filter { it > 2 }.map { it * 2 }

上面的写法很简单,在处理集合时,类似于上面的操作能帮我们解决大部分的问题。但是,当 list 中的元素非常多的时候(比如超过 10 万),上面的操作在处理集合的时候就会显得比较低效。

filter 方法和 map 方法都会返回都会返回一个新的集合,也就是说上面的操作会产生两个临时集合,因为 list 会先调用 filter 方法,然后产生的集合会再次调用 map 方法。如果 list 中的元素非常多,这将是一笔不小的开销。为了解决这个问题,Sequence(序列)就出现了。序列可以避免创建这些临时的中间对象。

以下是序列的使用:

val list = listOf(1, 2, 3, 4, 5)
list.asSequence().filter { it > 2 }.map { it * 2 }.toList()

首先通过 asSequence() 方法将列表转换为一个序列,然后在这个序列上进行相应的操作,最后通过 toList() 方法将序列转换为列表。

将 list 转换为序列,在很大程度上就提高了上面操作集合的效率。这是因此在使用序列的时候,filter 方法和 map 方法的操作都没有创建额外的集合,这样当集合中的元素数量巨大的时候,就减少了大部分开销。

在 Kotlin 中,序列中元素的求值是惰性的,这就意味着在利用序列进行链式求值时,不需要像操作普通集合那样,每进行一次求值操作,就产生一个新的集合保存中间数据。那么惰性又是什么意思呢?

在编程语言理论中,惰性求值(Lazy Evaluation)表示一种在需要时才进行求值的计算方式。在使用惰性求值的时候,表达式不在它被绑定到变量之后就立即求值,而是在该值被取用时才去求值。通过这种方式,不仅能得到性能上的提升,还有一个重要的好处就是它可以构造出一个无限的数据类型。

通过上面的定义我们可以知道惰性求值的两个好处,一个是优化性能,另一个就是能构造出无限的数据类型。

2 序列的操作方式

我们知道序列中元素的求值方式是采用惰性求值的。那么,惰性求值在序列中是如何体现的呢?

以下面的代码为例:

list.asSequence().filter { it > 2 }.map { it * 2 }.toList()

在这个例子中,我们对序列总共执行了两类操作,第一类:

filter { it > 2 }.map { it * 2 }

filter 和 map 的操作返回的都是序列,我们将这类操作称为中间操作。还有一类:

toList()

这一类操作序列转换为 List,我们将这类操作称为末端操作。其实,Kotlin 中序列的操作就分为两类,一类是中间操作,另一类则是末端操作。

2.1 中间操作

在对普通集合进行链式操作的时候,有些操作会产生中间集合,当用这类操作来对序列进行求值的时候,它们就被称为中间操作,比如上面的 filter 和 map。

每一次中间操作返回的都是一个序列,产生的新序列内部知道如何去变换原来序列中的元素。中间操作都是采用惰性求值的, 比如:

list.asSequence().filter {println("filter $it")it > 2
}.map {println("map $it")it * 2
}

执行,没有打印,说明上面的操作中的 println 方法根本就没有执行,这说明 filter 方法和 map 方法的执行被延迟了,这就是惰性求值的体现。

惰性求值仅仅在该值被需要的时候才会真正去求值。那么这个“被需要”的状态该怎么触发呢?这就是另外一个操作了——末端操作。

2.2 末端操作

在对集合进行操作的时候,大部分情况下,我们在意的只是结果,而不是中间过程。

末端操作就是一个返回结果的操作,它的返回值不能是序列,必须是一个明确的结果,比如列表、数字、对象等表意明确的结果。末端操作一般都是放在链式操作的末尾,在执行末端操作的时候,会出发中间操作的延迟计算,也就是将被需要这个状态打开了。

下面给上面的例子加上末端操作:

list.asSequence().filter {println("filter $it")it > 2
}.map {println("map $it")it * 2
}.toList()//filter 1
//filter 2
//filter 3
//map 3
//filter 4
//map 4
//filter 5
//map 5

可以看到,所有的中间操作都被执行了。

如果不用序列而是用列表来实现会有什么不同之处:

list.filter {println("filter $it")it > 2
}.map {println("map $it")it * 2
}.toList()//filter 1
//filter 2
//filter 3
//filter 4
//filter 5
//map 3
//map 4
//map 5

通过对比上面的结果,可以发现,普通集合在进行链式操作的时候会现在 list 上调用 filter,然后产生一个结果列表,接下来 map 就在这个结果列表上进行操作。而序列不一样,序列在执行链式操作时,会将所有的操作都引用在一个元素上,也就是说,第 1 个元素执行完所有的操作之后,第 2 个元素在去执行所有的操作,以此类推。

反映扫上面的这个例子,就是第 1 个元素执行了 filter 之后再去执行 map,然后,第 2 个元素也是这样。通过上面序列的返回结果可以知道,由于列表中的元素 1、2 没有满足 filter 操作中大于 2 的条件,所以接下来的 map 操作就不会去执行了。所以,当我们使用序列的时候,如果 filter 和 map 的位置是可以相互调换的话,应该优先使用 filter,这样会减少一部分开销。

3 序列可以是无限的

惰性求值最大的好处就是可以构造出一个无限的数据类型。

那么我们是否可以使用序列来构造一个无限的数据类型呢?答案是肯定的。常见的无限数据类型是什么呢?数列,比如自然数数列就是一个无限的数列。

那么如何去实现一个自然数列呢?采用一般的列表肯定是不行的,因为构建一个列表必须列举出列表中元素,而我们是么有办法将自然数全部列举出来。

自然数是有一定规律的,就是后一个数永远是前一个数加 1 的结果,我们只需要实现一个列表,让这个列表描述这种规律,那么也就是相当于实现了一个无限的自然数数列。Kotlin 为我们提供了这样一个方法,去创建无限的数列:

val naturalNumList = generateSequence(0) { it + 1 }

通过上面着一行代码,通过调用 generateSequence 就非常简单地实现了自然数数列。

我们知道序列是惰性求值的,所以上面创建的序列是不会把所有的自然数都列举出来的,只有在我们调用一个末端操作的时候,才去列举我们所需要的列表。

比如我们要从这个自然数列表中取出前 10 个自然数:

val list = naturalNumList.takeWhile { it <= 9 }.toList()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

关于无限序列这一点,我们不能将一个无限的数据结构通过穷举的方式呈现出来,而只是实现了一种表示无限的状态,让我们在使用的时候感觉它是无限的。

4 序列与 Java 8 Stream 对比

序列看上去就和 Java 8 中的流(Stream)比较类似。下面就列举一些 Java 8 Stream 中比较常见的特性,并与 Kotlin 中的序列进行比较。

4.1 Java 也能使用函数风格 API

在 Java 8 出来之后,在 Java 中也能像在 Kotlin 中那样操作集合了:

students.stream().filter(it -> it.sex == "m").collect(toList());

在上面的 Java 代码中,我们通过使用 stream 就能够使用类似于 filter 这种简洁的函数式 API 了。

但是相比于 Kotlin,Java 的这种操作方式还是有些繁琐,因为如果要对集合使用这种 API,就必须先将集合转换为 stream,操作完成之后,还要将 stream 转换为 List,这种操作有点类似于 Kotlin 的序列。这是因为 Java 8 的流和 Kotlin 中的序列一样,也是惰性求值的,这就意味着 Java 8 的流也是存在中间操作和末端操作的,所以必须通过上面的一系列转换才行。

4.2 Stream 是一次性的

与 Kotlin 的序列不同,Java 8 中的流是一次性的。意思就是说,如果我们创建了一个 Stream,我们只能在这个 Stream 上遍历一次。这就和迭代器很相似,当我们遍历万之后,这个流就相当于被消费掉了,我们必须再创建一个新的 Stream 才能再遍历一次。

Stream<Student> studentsStream = students.stream();
studentsStream.filter(it -> it.sex == "m").collect(toList());
studentsStream.filter(it -> it.sex == "f").collect(toList());
4.3 Stream 能够并行处理数据

Java 8 中的流非常强大,其中有一个非常重要的特性就是 Java 8 Stream 能够在多核架构上并行的进行流处理。比如将前面的例子转换为并行处理的方式如下:

students.paralleStream().filter(it -> it.sex == "m").collect(toList());

相关文章:

Kotlin 中的惰性集合

1 通过序列提高效率 首先看以下代码&#xff1a; val list listOf(1, 2, 3, 4, 5) list.filter { it > 2 }.map { it * 2 }上面的写法很简单&#xff0c;在处理集合时&#xff0c;类似于上面的操作能帮我们解决大部分的问题。但是&#xff0c;当 list 中的元素非常多的时…...

2024年React初学者入门路线指南

在这篇文章中&#xff0c;我们一步一步探索了如何从零基础开始学习React&#xff0c;并逐渐成长为一名初级开发者。通过理解基础概念、实践构建静态和动态项目&#xff0c;最终发展到创建复杂的应用程序并加入到个人作品集中&#xff0c;您现在已经准备好迈向React开发者的职业…...

【Java基础】了解Java安全体系JCA,使用BouncyCastle的ED25519算法生成密钥对、数据签名

文章目录 一.Java安全体系结构二.JCA和JCE三.CSP(加密服务提供程序)与Engine类1.CSP2.Engine类如何使用引擎类 四.查看当前JDK支持的算法服务提供商(Provider)五.BouncyCastle是什么六.如何使用BouncyCastle&#xff1f;七.bouncycastle实现ED25519工具类 一.Java安全体系结构 …...

SQL Server创建存储过程

使用以下语句创建一个存储过程&#xff1a; CREATE PROCEDURE [schema_name.]procedure_nameparameter1 datatype,parameter2 datatype,... AS BEGIN-- 存储过程的逻辑代码-- 可以包含SQL语句、控制流语句、变量声明等-- 示例&#xff1a;查询表中的数据SELECT column1, colum…...

GraphPad Prism 10:一站式数据分析解决方案

GraphPad Prism 10是一款功能强大的数据分析和可视化软件&#xff0c;广泛应用于生命科学研究、医学、生物、化学等多个领域。以下是对其详细功能的介绍&#xff1a; 首先&#xff0c;GraphPad Prism 10具有出色的数据可视化功能。它支持各种类型的图表和图形&#xff0c;包括…...

前端基础篇-深入了解 Ajax 、Axios

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Ajax 概述 2.0 Axios 概述 3.0 综合案例 1.0 Ajax 概述 通过 Ajax 可以给服务器发送请求&#xff0c;并获取服务器响应的数据。异步交互是指&#xff0c;可以在不…...

是德科技keysight N1912A双通道功率计

181/2461/8938产品概述&#xff1a; Keysight(原Agilent) N1912A P系列双通道功率计可提供峰值、峰均比、平均功率、上升时间、下降时间、最大功率值、最小功率值以及宽带信号的统计数据。 Keysight(原Agilent) N1912A P系列双通道功率计, 可提供峰值、峰均比、平均功率、上升…...

怿星科技Neptune CHT-S测试系统,让智能座舱测试更加高效便捷

随着汽车“智能化”浪潮的推进&#xff0c;汽车的智能化水平正在持续刷新行业认知。在这股智能化潮流中&#xff0c;智能座舱作为客户体验最为直观的部分&#xff0c;其重要性不言而喻。倘若座舱设备出现死机、黑屏、卡顿等现象&#xff0c;都将对客户的使用体验产生非常大的影…...

Vscode初建Vue时几个需要注意的问题

首先放图 注意点1.打开文件夹时&#xff0c;可以是VUE2 或者其他&#xff0c;但不能是VUE&#xff0c;会报错 注意点2.终端输入命令“npm init -y" npm init -y -y 的含义&#xff1a;yes的意思&#xff0c;在init的时候省去了敲回车的步骤&#xff0c;生成的默认的packag…...

最长不下降子序列

问题描述&#xff1a; 统计一个数组中的最长不下降子序列。 示例&#xff1a; 输入&#xff1a;14 输入&#xff1a;13 7 9 16 38 24 37 18 44 19 21 22 63 15 输出&#xff1a;8&#xff08;其中是7 9 16 18 19 21 22 63&#xff09; 方法&#xff1a;借鉴B站UP主T_zhao…...

QT gridlayout 循环设置组件,表格也通用 已解决

在需求中。经常遇到&#xff0c;表格 展示需求。 几乎都是json格式的。 // 列表配置文件QJsonArray listJsonArray getCfgJsonData("details_tab_table_config.json");if (listJsonArray.isEmpty()){return;}ui->gridWidget->setMaximumSize(QSize(310, 180)…...

后端前行Vue之路(一):初识Vue

1.Vue是什么 Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。另一方…...

C#、.NET版本、Visual Studio版本对应关系及Visual Studio老版本离线包下载地址

0、写这篇文章的目的 由于电脑的环境不同&#xff0c;对于一个老电脑找到一个适配的vscode环境十分不易。总结一下C#、.NET、Visual Studio版本的对应关系&#xff0c;及各个版本Visual Studio的下载地址供大家参考 1、C#、.NET版本、Visual Studio版本对应关系如下 2、Visua…...

yarn安装包时报错error Error: certificate has expired

安装教程&#xff1a; 配置镜像地址&#xff1a; npm config set registry https://registry.npmmirror.com//镜像&#xff1a;https://developer.aliyun.com/mirror/NPM 安装yarn&#xff1a; npm install --global yarn查看版本&#xff1a; yarn --version卸载&#xff…...

手机可以格式化存储卡吗?格式化以后出现什么情况

随着智能手机的普及&#xff0c;存储卡&#xff08;如SD卡、MicroSD卡等&#xff09;已成为手机存储扩展的重要工具。然而&#xff0c;在使用过程中&#xff0c;我们有时可能会遇到需要格式化存储卡的情况。那么&#xff0c;手机能否直接格式化存储卡呢&#xff1f;格式化后存储…...

亚马逊AWS展示高效纠错的全新量子比特!

亚马逊网络服务公司&#xff08;AWS&#xff09;在量子计算的纠错技术领域取得了显著成就&#xff0c;极大地简化了量子系统的复杂性和资源需求。他们的研究人员通过采用“双轨擦除”量子比特&#xff08;dual-rail erasure qubit&#xff09;技术&#xff0c;有效地克服了量子…...

FEX-Emu在Debian/Ubuntu系统使用

FEX-Emu在Debian/Ubuntu系统使用 1. Debootstrap子系统安装&#xff08;可选&#xff09;2. Debian/Ubuntu依赖包安装3. 获取FEX-Emu源码并编译4. 根文件系统RootFS安装5. 基于 FEX-Emu 运行应用 1. Debootstrap子系统安装&#xff08;可选&#xff09; sudo apt-get install …...

docker 不同架构镜像融合问题解决

1、背景 docker 作为目前容器的标准之一&#xff0c;但是对于多种架构的平台的混合编译支撑不是很好。因此衍生了镜像融合&#xff0c;分别将多种不同的架构构建好&#xff0c;然后将镜像进行融合上传。拉取镜像的会根据当前系统的架构拉取不同的镜像&#xff0c;也可以通过 -…...

windows_anaconda 安装pytorch

查看CUDA版本 cmd nvidia-smi # NVIDIA-SMI 546.56 Driver Version: 546.56 CUDA Version: 12.3nvcc --version # nvcc: NVIDIA (R) Cuda compiler driver # Copyright (c) 2005-2023 NVIDIA Corporation # Built on Wed_Nov_22_10:30:42_Pacific_Standard_Time_2023 # C…...

IP SSL证书注册流程

使用IP地址申请SSL证书&#xff0c;需要用公网IP地址申请&#xff0c;申请之前确保直接的IP地址可以开放80或者443端口两者选择1个就好&#xff0c;端口不需要一直开放&#xff0c;只要认证的几分钟内开放就可以了&#xff0c;然后IP地址根目录可以上传txt文件。 IP SSL证书认…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

免费数学几何作图web平台

光锐软件免费数学工具&#xff0c;maths,数学制图&#xff0c;数学作图&#xff0c;几何作图&#xff0c;几何&#xff0c;AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官

。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量&#xff1a;setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...

[USACO23FEB] Bakery S

题目描述 Bessie 开了一家面包店! 在她的面包店里&#xff0c;Bessie 有一个烤箱&#xff0c;可以在 t C t_C tC​ 的时间内生产一块饼干或在 t M t_M tM​ 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC​,tM​≤109)。由于空间…...

Linux基础开发工具——vim工具

文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...

RabbitMQ 各类交换机

为什么要用交换机&#xff1f; 交换机用来路由消息。如果直发队列&#xff0c;这个消息就被处理消失了&#xff0c;那别的队列也需要这个消息怎么办&#xff1f;那就要用到交换机 交换机类型 1&#xff0c;fanout&#xff1a;广播 特点 广播所有消息​​&#xff1a;将消息…...