关于android:影响性能的-Kotlin-代码一

3次阅读

共计 4126 个字符,预计需要花费 11 分钟才能阅读完成。

Kotlin 高级函数的个性不仅让代码可读性更强,更加简洁,而且还进步了生产效率,然而简洁的背地是有代价的,暗藏着不能被忽视的老本,特地是在低端机上,这种老本会被放大,因而咱们须要去钻研 kotlin 语法糖背地的魔法,抉择适合的语法糖,尽量避免这些坑。

Lambda 表达式

Lambda 表达式语法简洁,防止了简短的函数申明,代码如下。

fun requestData(type: Int, call: (code: Int, type: Int) -> Unit) {call(200, type)
}

Lambda 表达式语法尽管简洁,然而暗藏着两个性能问题。

  • 每次调用 Lambda 表达式,都会创立一个对象

图中标记 1 所示的中央,波及一个字节码类型的知识点。

标识符 含意
I 根本类型 int
L 对象类型,以分号结尾,如 Lkotlin/jvm/functions/Function2;

Lambda 表达式 call: (code: Int, type: Int) -> Unit 作为函数参数,传递到函数中,Lambda 表达式会继承 kotlin/jvm/functions/Function2 , 每次调用都会创立一个 Function2 对象,如图中标记 2 所示的中央。

  • Lambda 表达式隐含主动装箱和拆箱过程

正如你所见 lambda 表达式存在装箱和拆箱的开销,会将 int 转成 Integer,之后进行一系列操作,最初会将 Integer 转成 int

如果想要防止 Lambda 表达式函数对象的创立及装箱拆箱开销,能够应用 inline 内联函数,间接执行 lambda 表达式函数体。

Inline 修饰符

Inline 内联函数的作用:晋升运行效率,调用被 inline 修饰符标记的函数,会把函数内的代码放到调用的中央。

如果浏览过 Koin 源码的敌人,应该会发现 inline 都是和 lambda 表达式和 reified 修饰符配套在一起应用的,如果只应用 inline 修饰符标记一般函数,Android Studio 也会给一个大大大的正告。

编译器倡议咱们在含有 lambda 表达式作为形参的函数中应用内联,既然 Inline 修饰符能够晋升运行效率,为什么编译器会给咱们一个正告?这是为了避免 inline 操作符滥用而带来的性能损失。

inline 修饰符实用于以下状况

  • inline 修饰符实用于把函数作为另一个函数的参数,例如高阶函数 filter、map、joinToString 或者一些独立的函数 repeat
  • inline 操作符适宜和 reified 操作符联合在一起应用
  • 如果函数体很短,应用 inline 操作符能够提高效率

Kotlin 遍历数组

这一大节次要介绍 Kotlin 数组,一起来看一下遍历数组都有几种形式。

  • 通过 forEach 遍历数组
  • 通过区间表达式遍历数组(..downTountil)
  • 通过 indices 遍历数组
  • 通过 withIndex 遍历数组

通过 forEach 遍历数组

先来看看通过 forEach 遍历数组,和其余的遍历数组的形式,有什么不同。

array.forEach {value ->}

反编译后:Integer[] var5 = array;
int var6 = array.length;
for(int var7 = 0; var7 < var6; ++var7) {Object element$iv = var5[var7];
 int value = ((Number)element$iv).intValue();
 boolean var10 = false;
}

正如你所见通过 forEach 遍历数组的形式,会创立额定的对象,并且存在装箱 / 拆箱开销,会占用更多的内存。

通过区间表达式遍历数组

在 Kotlin 中区间表达式有三种 ..downTountil

  • .. 关键字,示意左闭右闭区间
  • downTo 关键字,实现降序循环
  • until 关键字,示意左闭右开区间

..、downTo、until

for (value in 0..size - 1) {// case 1}

for (value in size downTo 0) {// case 2}

for (value in 0 until  size) {// case 3}

反编译后

// case 1 
if (value <= var4) {while(value != var4) {++value;}
}

// case 2
for(boolean var5 = false; value >= 0; --value) {
}

// case 3
for(var4 = size; value < var4; ++value) {}

如上所示 区间表达式 (..downTountil) 除了创立一些长期变量之外,不会创立额定的对象,然而区间表达式 和 step 关键字联合起来一起应用,就会存在内存问题。

区间表达式 和 step 关键字

step 操作的 ..downTountil,编译之后如下所示。

for (value in 0..size - 1 step 2) {// case 1}

for (value in 0 downTo size step 2) {// case 2}

反编译后:// case 1
var10000 = RangesKt.step((IntProgression)(new IntRange(var6, size - 1)), 2);
while(value != var4) {value += var5;}

// case 2
 var10000 = RangesKt.step(RangesKt.downTo(0, size), 2);
 while(value != var4) {value += var5;}

step 操作的 ..downTountil 除了创立一些长期变量之外,还会创立 IntRangeIntProgression 对象,会占用更多的内存。

通过 indices 遍历数组

indices 通过索引的形式遍历数组,每次遍历的时候通过索引获取数组外面的元素,如下所示。

for (index in array.indices) {
}

反编译后:for(int var4 = array.length; var3 < var4; ++var3) {}

通过 indices 遍历数组,编译之后的代码,除了创立了一些长期变量,并没有创立额定的对象。

通过 withIndex 遍历数组

withIndexindices 遍历数组的形式类似,通过 withIndex 遍历数组,不仅能够获取的数组索引,同时还能够获取到每一个元素。

for ((index, value) in array.withIndex()) {

}

反编译后:Integer[] var5 = array;
int var6 = array.length;
for(int var3 = 0; var3 < var6; ++var3) {int value = var5[var3];
}

正如你所看到的,通过 withIndex 形式遍历数组,尽管不会创立额定的对象,然而存在装箱 / 拆箱的开销

总结:

  • 通过 forEach 遍历数组的形式,会创立额定的对象,占用内存,并且存在装箱 / 拆箱开销
  • 通过 indices 和区间表达式 (..downTountil) 都不会创立额定的对象
  • 区间表达式 和 step 关键字联合一起应用,会有创立额定的对象的开销,占用更多的内存
  • 通过 withIndex 形式遍历数组,不会创立额定的对象,然而存在装箱 / 拆箱的开销

尽量少应用 toLowerCase 和 toUpperCase 办法

这一大节内容,在我之前的文章中分享过,然而这也是很多小伙伴,遇到最多的问题,所以独自拿进去在剖析一次

当咱们比拟两个字符串,须要疏忽大小写的时候,通常的写法是调用 toLowerCase() 办法或者 toUpperCase() 办法转换成大写或者小写,而后在进行比拟,然而这样的话有一个不好的中央,每次调用 toLowerCase() 办法或者 toUpperCase() 办法会创立一个新的字符串,而后在进行比拟。

调用 toLowerCase() 办法

fun main(args: Array<String>) {//    use toLowerCase()
    val oldName = "Hi dHL"
    val newName = "hi Dhl"
    val result = oldName.toLowerCase() == newName.toLowerCase()

//    or use toUpperCase()
//    val result = oldName.toUpperCase() == newName.toUpperCase()
}

toLowerCase() 编译之后的 Java 代码

如上图所示首先会生成一个新的字符串,而后在进行字符串比拟,那么 toUpperCase() 办法也是一样的如下图所示。

toUpperCase() 编译之后的 Java 代码

这里有一个更好的解决方案,应用 equals 办法来比拟两个字符串,增加可选参数 ignoreCase 来疏忽大小写,这样就不须要调配任何新的字符串来进行比拟了。

fun main(args: Array<String>) {
    val oldName = "hi DHL"
    val newName = "hi dhl"
    val result = oldName.equals(newName, ignoreCase = true)
}

equals 编译之后的 Java 代码

应用 equals 办法并没有创立额定的对象,如果遇到须要比拟字符串的时候,能够应用这种办法,缩小额定的对象创立。

by lazy

by lazy 作用是懒加载,保障首次拜访的时候才初始化 lambda 表达式中的代码,by lazy 有三种模式。

  • LazyThreadSafetyMode.NONE 仅仅在单线程
  • LazyThreadSafetyMode.SYNCHRONIZED 在多线程中应用
  • LazyThreadSafetyMode.PUBLICATION 不罕用

LazyThreadSafetyMode.SYNCHRONIZED 是默认的模式,多线程中应用,能够保障线程平安,然而会有 double check + lock 性能开销,代码如下图所示。

如果是在主线程中应用,和初始化相干的逻辑,倡议应用 LazyThreadSafetyMode.NONE 模式,缩小不必要的开销。

学习资源举荐👉

《Kotlin 入门教程指南》,篇幅无限,下方有收费支付形式!

这份完整版的《Kotlin 入门教程指南》PDF 版电子书,点这里能够看到全部内容 。或者点击【 这里】查看获取形式。

正文完
 0