(4)Kotlin/Js For Harmony——解决集合卡顿(HashMap, HashSet, List)

50 阅读2分钟

Kotlin/Js 的HashMap用在鸿蒙上非常慢,究其原因是hashCode 的实现慢。这个也好理解,HashMap会被编译成js代码,因此hashCode 的实现也是纯js的,自然比不过鸿蒙提供的被引擎深度优化的Map了。并且随着存入Map 的 key 的长度增加,Kotlin/Js 的HashMap 性能呈指数级劣化。

HarmonyMutableMap, HarmonyMutableSet

上面提到了Kotlin/Js HashMap 慢的原因。所以要解决HashMap慢,就需要用鸿蒙原生Map 来替代Kotlin/Js的HashMap。

执行起来也非常简单,即实现MutableMap接口提供桥接到鸿蒙Map的HarmonyMutableMap,代码如下:

class HarmonyMutableMap<K, V> : MutableMap<K, V> {
    // 桥接原生map
    private val map = js("new Map()") 

    override fun get(key: K): V? = map.get(key) as V?
    
    override fun put(key: K, value: V): V? {
        val oldValue = get(key)
        map.set(key, value)

        return oldValue
    }
    // 其余操作符类似,这里不再赘述
    ......
}

HashSet的处理方式同理,这里不再赘述。

HarmonyMutableList

MutableList 有一个优化点是 asJsReadonlyArrayView() 方法。 原始实现是每次调用都会创建一个新的JsReadonlyArray对象,快速调用时会快速大量创建对象。

我们注意到 MutableList 还有一个 asJsArrayView() 方法,实现如下:

 override fun asJsArrayView(): JsArray<E> = array.unsafeCast<JsArray<E>>()

可以看到它直接返回了原始Array, 没有额外创建对象,所以我们可以用 asJsArrayView 的实现来代替asJsReadonlyArrayView的原始实现。

为了达到这个目的,我们实现自己的MutableList, 即 HarmonyMutableList :

class HarmonyMutableList<E> : MutableList<E>{
    private val origin: ArrayList<E>

    constructor() {
        this.origin = ArrayList()
    }

    constructor(origin: Collection<E>){
        this.origin = ArrayList(origin)
    }

    override val size: Int
        get() = origin.size

    override fun add(element: E): Boolean {
        return origin.add(element)
    }


	override fun asJsArrayView(): JsArray<E> {
        return origin.asJsArrayView()
    }

    override fun asJsReadonlyArrayView(): JsReadonlyArray<E> {
        return asJsArrayView()
    }
    // 省略部分实现

优化效果

黄线是我们自己实现的HarmonyMap, 红线是Kotlin HashMap, key 长度为100的时候,性能差距在40倍,并且根据key长度增加,性能差距越来越大

image.png


关于「解决集合卡顿(HashMap, HashSet, List)」的介绍就告一段落了,如果大家在使用过程中有任何问题,欢迎留言讨论。 Android工程师的kmp(kotlin/js) for harmony开发指南 这一系列文章旨在系统性的提供一套完整的Kotlin/Js For Harmony的解决方案。后续系列文章会介绍如何复用ViewModel,序列化卡顿优化,鸿蒙开发套件,架构设计思路等等,欢迎关注!