IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 为数不多的人知道的-Kotlin-技巧以及-原理解析(二),一年后斩获腾讯T3 -> 正文阅读

[移动开发]为数不多的人知道的-Kotlin-技巧以及-原理解析(二),一年后斩获腾讯T3

val name = target.ifEmpty { “dhl” }
复制代码

其原理跟我们使用 if 表达式是一样的,来分析一下源码。

public inline fun <C, R> C.ifEmpty(defaultValue: () -> R): R where C : CharSequence, C : R =
if (isEmpty()) defaultValue() else this
复制代码

ifEmpty?方法是一个扩展方法,接受一个 lambda 表达式 defaultValue ,如果是空字符串,返回 defaultValue,否则不为空,返回调用者本身。

除了?ifEmpty?方法,Kotlin 库中还封装很多其他非常有用的字符串,例如:将字符串转为数字。常见的写法如下所示:

val input = “123”
val number = input.toInt()
复制代码

其实这种写法存在一定问题,假设输入字符串并不是纯数字,例如?123ddd?等等,调用?input.toInt()?就会报错,那么有没有更好的写法呢?如下所示。

val input = “123”
// val input = “123ddd”
// val input = “”
val number = input.toIntOrNull() ?: 0
复制代码

避免将解构声明和数据类一起使用

这是 Kotlin 团队一个建议:避免将解构声明和数据类一起使用,如果以后往数据类添加新的属性,很容易破坏代码的结构。我们一起来思考一下,为什么 Kotlin 官方会这么说,我先来看一个例子:数据类和解构声明的使用。

// 数据类
data class People(
val name: String,
val city: String
)

fun main(args: Array) {
// 编译测试
printlnPeople(People(“dhl”, “beijing”))
}

fun printlnPeople(people: People) {
// 解构声明,获取 name 和 city 并将其输出
val (name, city) = people
println(“name: ${name}”)
println(“city: ${city}”)
}
复制代码

输出结果如下所示:

name: dhl
city: beijing
复制代码

随着需求的变更,需要给数据类 People 添加一个新的属性 age。

// 数据类,增加了 age
data class People(
val name: String,
val age: Int,
val city: String
)

fun main(args: Array) {
// 编译测试
printlnPeople(People(“dhl”, 80, “beijing”))
}
复制代码

此时没有更改解构声明,也不会有任何错误,编译输出结果如下所示:

name: dhl
city: 80
复制代码

得到的结果并不是我们期望的,此时我们不得不更改解构声明的地方,如果代码中有多处用到了解构声明,因为增加了新的属性,就要去更改所有使用解构声明的地方,这明显是不合理的,很容易破坏代码的结构,所以一定要避免将解构声明和数据类一起使用。当我们使用不规范的时候,并且编译器也会给出警告,如下图所示。

[图片上传失败…(image-bb59b4-1594646872317)]

文件的扩展方法

Kotlin 提供了很多文件扩展方法?Extensions for?java.io.Reade?:forEachLine?、?readLines?、?readText?、?useLines?等等方法,帮助我们简化文件的操作,而且使用完成之后,它们会自动关闭,例如?useLines?方法:

File(“dhl.txt”).useLines { line ->
println(line)
}
复制代码

useLines?是 File 的扩展方法,调用?useLines?会返回一个文件中所有行的 Sequence,当文件内容读取完毕之后,它会自动关闭,其源码如下。

public inline fun File.useLines(charset: Charset = Charsets.UTF_8, block: (Sequence) -> T): T =
bufferedReader(charset).use { block(it.lineSequence()) }
复制代码

  • useLines?是 File 的一个扩展方法
  • useLines?接受一个 lambda 表达式 block
  • 调用了 BufferedReader 读取文件内容,之后调用 block 返回文件中所有行的 Sequence 给调用者

那它是如何在读取完毕自动关闭的呢,核心在?use?方法里面,在?useLines?方法内部调用了?use方法,use?方法也是一个扩展方法,源码如下所示。

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
when {
apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
this == null -> {}
exception == null -> close()
else ->
try {
close()
} catch (closeException: Throwable) {
// cause.addSuppressed(closeException) // ignored here
}
}
}
}
复制代码

其实很简单,调用?try...catch...finally?最后在 finally 内部进行 close。其实我们也可以根据源码实现一个通用的异常捕获方法。

inline fun <T, R> T.dowithTry(block: (T) -> R) {
try {
block(this)
} catch (e: Throwable) {
e.printStackTrace()
}
}

// 使用方式
dowithTry {
// 添加会出现异常的代码, 例如
val result = 1 / 0
}
复制代码

当然这只是一个非常简单的异常捕获方法,在实际项目中还有很多需要去处理的,比如说异常信息需不需要返回给调用者等等。

在上文中提到了调用?useLines?方法返回一个文件中所有行的 Sequence,为什么 Kolin 会返回 Sequence,而不返回 Iterator?

Sequence 和 Iterator 不同之处

为什么 Kolin 会返回 Sequence,而不返回 Iterator?其实这个核心原因由于 Sequence 和 Iterator 实现不同导致?内存?和?性能?有很大的差异。

接下来我们围绕这两个方面来分析它们的性能,Sequences(序列) 和 Iterator(迭代器) 都是一个比较大的概念,本文的目的不是去分析它们,所以在这里不会去详细分析 Sequence 和 Iterator

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

,只会围绕着?内存?和?性能?两个方面去分析它们的区别,让我们有一个直观的印象。更多信息可以查看国外一位大神写的文章?Prefer Sequence for big collections with more than one processing step

Sequence 和 Iterator 从代码结构上来看,它们非常的相似如下所示:

interface Iterable {
operator fun iterator(): Iterator
}

interface Sequence {
operator fun iterator(): Iterator
}
复制代码

除了代码结构之外,Sequences(序列) 和 Iterator(迭代器) 它们的实现完全不一样。

Sequences(序列)

Sequences 是属于懒加载操作类型,在 Sequences 处理过程中,每一个中间操作不会进行任何计算,它们只会返回一个新的 Sequence,经过一系列中间操作之后,会在末端操作?toList?或?count?等等方法中进行最终的求职运算,如下图所示。

[图片上传失败…(image-643844-1594646872316)]

在 Sequences 处理过程中,会对单个元素进行一系列操作,然后在对下一个元素进行一系列操作,直到所有元素处理完毕。

val data = (1…3).asSequence()
.filter { print("FKaTeX parse error: Expected '}', got 'EOF' at end of input: ….map { print("Mit, "); it * 2 }
.forEach { print("E$it, ") }
println(data)

// 输出 F1, M1, E2, F2, F3, M3, E6
复制代码

[图片上传失败…(image-28a57e-1594646872316)]

如上所示:在 Sequences 处理过程中,对 1 进行一系列操作输出?F1, M1, E2, 然后对 2 进行一系列操作,依次类推,直到所有元素处理完毕,输出结果为?F1, M1, E2, F2, F3, M3, E6

在 Sequences 处理过程中,每一个中间操作( map、filter 等等 )不进行任何计算,只有在末端操作( toList、count、forEach 等等方法 ) 进行求值运算,如何区分是中间操作还是末端操作,看方法的返回类型,中间操作返回的是 Sequence,末端操作返回的是一个具体的类型( List、int、Unit 等等 )源码如下所示。

// 中间操作 map ,返回的是 Sequence
public fun <T, R> Sequence.map(transform: (T) -> R): Sequence {
return TransformingSequence(this, transform)
}

// 末端操作 toList 返回的是一个具体的类型(List)
public fun Sequence.toList(): List {
return this.toMutableList().optimizeReadOnlyList()
}

// 末端操作 forEachIndexed 返回的是一个具体的类型(Unit)
public inline fun Sequence.forEachIndexed(action: (index: Int, T) -> Unit): Unit {
var index = 0
for (item in this) action(checkIndexOverflow(index++), item)
}
复制代码

  • 如果是中间操作 map、filter 等等,它们返回的是一个 Sequence,不会进行任何计算
  • 如果是末端操作 toList、count、forEachIndexed 等等,返回的是一个具体的类型( List、int、Unit 等等 ),会做求值运算

Iterator(迭代器)

在 Iterator 处理过程中,每一次的操作都是对整个数据进行操作,需要开辟新的内存来存储中间结果,将结果传递给下一个操作,代码如下所示:

val data = (1…3).asIterable()
.filter { print("FKaTeX parse error: Expected '}', got 'EOF' at end of input: ….map { print("Mit, "); it * 2 }
.forEach { print("E$it, ") }
println(data)

// 输出 F1, F2, F3, M1, M3, E2, E6
复制代码

[图片上传失败…(image-dae99a-1594646872315)]

如上所示:在 Iterator 处理过程中,调用 filter 方法对整个数据进行操作输出?F1, F2, F3,将结果存储到 List 中, 然后将结果传递给下一个操作 ( map ) 输出?M1, M3?将新的结果在存储的 List 中, 直到所有操作处理完毕。

// 每次操作都会开辟一块新的空间,存储计算的结果
public inline fun Iterable.filter(predicate: (T) -> Boolean): List {
return filterTo(ArrayList(), predicate)
}

// 每次操作都会开辟一块新的空间,存储计算的结果
public inline fun <T, R> Iterable.map(transform: (T) -> R): List {
return mapTo(ArrayList(collectionSizeOrDefault(10)), transform)
}
复制代码

对于每次操作都会开辟一块新的空间,存储计算的结果,这是对内存极大的浪费,我们往往只关心最后的结果,而不是中间的过程。

了解完 Sequences 和 Iterator 不同之处,接下里我们从?性能?和?内存?两个方面来分析 Sequences 和 Iterator。

Sequences 和 Iterator 性能对比

分别使用 Sequences 和 Iterator 调用它们各自的 filter、map 方法,处理相同的数据的情况下,比较它们的执行时间。

使用 Sequences :

val time = measureTimeMillis {
(1…10000000 * 10).asSequence()
.filter { it % 2 == 1 }
.map { it * 2 }
.count()
}

println(time) // 1197
复制代码

使用 Iterator :

val time2 = measureTimeMillis {
(1…10000000 * 10).asIterable()
.filter { it % 2 == 1 }
.map { it * 2 }
.count()
}

println(time2) // 23641
复制代码

Sequences 和 Iterator 处理时间如下所示:

SequencesIterator
119723641

这个结果是很让人吃惊的,Sequences 比 Iterator 快 19 倍,如果数据量越大,它们的时间差距会越来越大,当我们在读取文件的时候,可能会进行一系列的数据操作?dropfilter?等等,所以 Kotlin 库函数?useLines?等等方法会返回 Sequences,因为它们更加的高效。

Sequences 和 Iterator 内存对比

这里使用了?Prefer Sequence for big collections with more than one processing step?文章的一个例子。

有 1.53 GB 犯罪分子的数据存储在文件中,从文件中找出有多少犯罪分子携带大麻,分别使用 Sequences 和 Iterator,我们先来看一下如果使用 Iterator 处理会怎么样(这里调用?readLines函返回?List<String>

File(“ChicagoCrimes.csv”).readLines()
.drop(1) // Drop descriptions of the columns
.mapNotNull { it.split(",").getOrNull(6) }
// Find description
.filter { “CANNABIS” in it }
.count()
.let(::println)
复制代码

运行完之后,你将会得到一个意想不到的结果?OutOfMemoryError

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
复制代码

调用 readLines 函返回一个集合,有 3 个中间操作,每一个中间操作都需要一块空间存储 1.53 GB 的数据,它们需要占用超过 4.59 GB 的空间,每次操作都开辟了一块新的空间,这是对内存巨大浪费。如果我们使用序列 Sequences 会怎么样呢?(调用?useLines?方法返回的是一个 Sequences)。

File(“ChicagoCrimes.csv”).useLines { lines ->
// The type of lines is Sequence
lines
.drop(1) // Drop descriptions of the columns
.mapNotNull { it.split(",").getOrNull(6) }
// Find description
.filter { “CANNABIS” in it }
.count()
.let { println(it) } // 318185
复制代码

没有出现?OutOfMemoryError?异常,共耗时 8.3 s,由此可见对于文件操作使用序列不仅能提高性能,还能减少内存的使用,从性能和内存这两面也解释了为什么 Kotlin 库的扩展方法?useLines等等,读取文件的时候使用 Sequences 而不使用 Iterator。

便捷的 joinToString 方法的使用

joinToString?方法提供了一组丰富的可选择项( 分隔符,前缀,后缀,数量限制等等 )可用于将可迭代对象转换为字符串。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-16 17:47:32  更:2021-12-16 17:49:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:25:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码