影響性能的 Kotlin 代碼(一)

要開始寫新的 Kotlin 系列了 「影響性能的 Kotlin 代碼」, 同時我也在寫另一個系列 「為數(shù)不多的人知道的 Kotlin 技巧及解析」,沒有看過的小伙伴麦向,可以點擊下方鏈接前去查看诵竭。

Kotlin 高級函數(shù)的特性不僅讓代碼可讀性更強卵慰,更加簡潔裳朋,而且還提高了生產(chǎn)效率鲤嫡,但是簡潔的背后是有代價的,隱藏著不能被忽視的成本惕耕,特別是在低端機上对扶,這種成本會被放大,因此我們需要去研究 kotlin 語法糖背后的魔法笼才,選擇合適的語法糖骡送,盡量避免這些坑摔踱。

Lambda 表達式

Lambda 表達式語法簡潔派敷,避免了冗長的函數(shù)聲明篮愉,代碼如下试躏。

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

Lambda 表達式語法雖然簡潔,但是隱藏著兩個性能問題。

  • 每次調(diào)用 Lambda 表達式弱判,都會創(chuàng)建一個對象

圖中標記 1 所示的地方昌腰,涉及一個字節(jié)碼類型的知識點固灵。

標識符 含義
I 基本類型 int
L 對象類型劫流,以分號結(jié)尾仍秤,如 Lkotlin/jvm/functions/Function2;

Lambda 表達式 call: (code: Int, type: Int) -> Unit 作為函數(shù)參數(shù)诗力,傳遞到函數(shù)中苇本,Lambda 表達式會繼承 kotlin/jvm/functions/Function2 , 每次調(diào)用都會創(chuàng)建一個 Function2 對象,如圖中標記 2 所示的地方俺夕。

  • Lambda 表達式隱含自動裝箱和拆箱過程

正如你所見 lambda 表達式存在裝箱和拆箱的開銷啥么,會將 int 轉(zhuǎn)成 Integer悬荣,之后進行一系列操作,最后會將 Integer 轉(zhuǎn)成 int嚼蚀。

如果想要避免 Lambda 表達式函數(shù)對象的創(chuàng)建及裝箱拆箱開銷管挟,可以使用 inline 內(nèi)聯(lián)函數(shù)导帝,直接執(zhí)行 lambda 表達式函數(shù)體您单。

Inline 修飾符

Inline (內(nèi)聯(lián)函數(shù)) 的作用:提升運行效率平酿,調(diào)用被 inline 修飾符標記的函數(shù)蜈彼,會把函數(shù)內(nèi)的代碼放到調(diào)用的地方挖垛。

如果閱讀過 Koin 源碼的朋友痢毒,應(yīng)該會發(fā)現(xiàn) inline 都是和 lambda 表達式和 reified 修飾符配套在一起使用的栋荸,如果只使用 inline 修飾符標記普通函數(shù)晌块,Android Studio 也會給一個大大大的警告。

編譯器建議我們在含有 lambda 表達式作為形參的函數(shù)中使用內(nèi)聯(lián)帅霜,既然 Inline 修飾符可以提升運行效率匆背,為什么編譯器會給我們一個警告? 這是為了防止 inline 操作符濫用而帶來的性能損失身冀。

inline 修飾符適用于以下情況

  • inline 修飾符適用于把函數(shù)作為另一個函數(shù)的參數(shù)钝尸,例如高階函數(shù) filter、map搂根、joinToString 或者一些獨立的函數(shù) repeat
  • inline 操作符適合和 reified 操作符結(jié)合在一起使用
  • 如果函數(shù)體很短珍促,使用 inline 操作符可以提高效率

Kotlin 遍歷數(shù)組

這一小節(jié)主要介紹 Kotlin 數(shù)組,一起來看一下遍歷數(shù)組都有幾種方式剩愧。

  • 通過 forEach 遍歷數(shù)組
  • 通過區(qū)間表達式遍歷數(shù)組(.. 成洗、 downTo 遥椿、 until)
  • 通過 indices 遍歷數(shù)組
  • 通過 withIndex 遍歷數(shù)組

通過 forEach 遍歷數(shù)組

先來看看通過 forEach 遍歷數(shù)組碴裙,和其他的遍歷數(shù)組的方式载慈,有什么不同寡具。

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 遍歷數(shù)組的方式,會創(chuàng)建額外的對象,并且存在裝箱/拆箱開銷,會占用更多的內(nèi)存。

通過區(qū)間表達式遍歷數(shù)組

在 Kotlin 中區(qū)間表達式有三種 ..downTo 抚笔、 until

  • .. 關(guān)鍵字训貌,表示左閉右閉區(qū)間
  • downTo 關(guān)鍵字儒飒,實現(xiàn)降序循環(huán)
  • until 關(guān)鍵字井誉,表示左閉右開區(qū)間

.. 、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) {
}

如上所示 區(qū)間表達式 ( .. 岸蜗、 downTountil) 除了創(chuàng)建一些臨時變量之外堂淡,不會創(chuàng)建額外的對象,但是區(qū)間表達式 和 step 關(guān)鍵字結(jié)合起來一起使用,就會存在內(nèi)存問題拌倍。

區(qū)間表達式 和 step 關(guān)鍵字

step 操作的 .. 泡孩、 downTo 环戈、 until, 編譯之后如下所示。

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 操作的 .. 徙赢、 downTo 西潘、 until 除了創(chuàng)建一些臨時變量之外,還會創(chuàng)建 IntRangeIntProgression 對象,會占用更多的內(nèi)存算柳。

通過 indices 遍歷數(shù)組

indices 通過索引的方式遍歷數(shù)組蔗蹋,每次遍歷的時候通過索引獲取數(shù)組里面的元素,如下所示。

for (index in array.indices) {
}

反編譯后:

for(int var4 = array.length; var3 < var4; ++var3) {
}

通過 indices 遍歷數(shù)組闺属, 編譯之后的代碼 乃摹,除了創(chuàng)建了一些臨時變量,并沒有創(chuàng)建額外的對象移盆。

通過 withIndex 遍歷數(shù)組

withIndexindices 遍歷數(shù)組的方式相似,通過 withIndex 遍歷數(shù)組绞愚,不僅可以獲取的數(shù)組索引叙甸,同時還可以獲取到每一個元素。

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 方式遍歷數(shù)組裆蒸,雖然不會創(chuàng)建額外的對象,但是存在裝箱/拆箱的開銷

總結(jié):

  • 通過 forEach 遍歷數(shù)組的方式糖驴,會創(chuàng)建額外的對象僚祷,占用內(nèi)存,并且存在裝箱 / 拆箱開銷
  • 通過 indices 和區(qū)間表達式 ( .. 贮缕、 downTo 辙谜、 until) 都不會創(chuàng)建額外的對象
  • 區(qū)間表達式 和 step 關(guān)鍵字結(jié)合一起使用, 會有創(chuàng)建額外的對象的開銷感昼,占用更多的內(nèi)存
  • 通過 withIndex 方式遍歷數(shù)組装哆,不會創(chuàng)建額外的對象,但是存在裝箱/拆箱的開銷

盡量少使用 toLowerCase 和 toUpperCase 方法

這一小節(jié)內(nèi)容抑诸,在我之前的文章中分享過烂琴,但是這也是很多小伙伴,遇到最多的問題蜕乡,所以單獨拿出來在分析一次

當我們比較兩個字符串奸绷,需要忽略大小寫的時候,通常的寫法是調(diào)用 toLowerCase() 方法或者 toUpperCase() 方法轉(zhuǎn)換成大寫或者小寫层玲,然后在進行比較号醉,但是這樣的話有一個不好的地方,每次調(diào)用 toLowerCase() 方法或者 toUpperCase() 方法會創(chuàng)建一個新的字符串辛块,然后在進行比較畔派。

調(diào)用 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 方法來比較兩個字符串尘盼,添加可選參數(shù) ignoreCase 來忽略大小寫憨愉,這樣就不需要分配任何新的字符串來進行比較了陶耍。

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

equals 編譯之后的 Java 代碼

使用 equals 方法并沒有創(chuàng)建額外的對象胶背,如果遇到需要比較字符串的時候誉简,可以使用這種方法审孽,減少額外的對象創(chuàng)建。

by lazy

by lazy 作用是懶加載躺孝,保證首次訪問的時候才初始化 lambda 表達式中的代碼享扔, by lazy 有三種模式。

  • LazyThreadSafetyMode.NONE 僅僅在單線程
  • LazyThreadSafetyMode.SYNCHRONIZED 在多線程中使用
  • LazyThreadSafetyMode.PUBLICATION 不常用

LazyThreadSafetyMode.SYNCHRONIZED 是默認的模式植袍,多線程中使用惧眠,可以保證線程安全,但是會有 double check + lock 性能開銷奋单,代碼如下圖所示锉试。

如果是在主線程中使用,和初始化相關(guān)的邏輯览濒,建議使用 LazyThreadSafetyMode.NONE 模式呆盖,減少不必要的開銷。

倉庫 KtKit 是用 Kotlin 語言編寫的小巧而實用的工具庫贷笛,包含了項目中常用的一系列工具, 正在逐漸完善中应又,如果你有興趣,想邀請你和我一起來完善這個庫乏苦。

如果這個倉庫對你有幫助株扛,請在倉庫右上角幫我 star 一下,非常感謝你的支持汇荐,同時也歡迎你提交 PR

如果有幫助 點個贊 就是對我最大的鼓勵

代碼不止洞就,文章不停

持續(xù)分享最新的技術(shù)


最后推薦我一直在更新維護的項目和網(wǎng)站:

  • 個人博客,將所有文章進行分類掀淘,歡迎前去查看 https://hi-dhl.com

  • 計劃建立一個最全旬蟋、最新的 AndroidX Jetpack 相關(guān)組件的實戰(zhàn)項目 以及 相關(guān)組件原理分析文章,正在逐漸增加 Jetpack 新成員革娄,倉庫持續(xù)更新倾贰,歡迎前去查看:AndroidX-Jetpack-Practice

  • LeetCode / 劍指 offer / 國內(nèi)外大廠面試題 / 多線程 題解,語言 Java 和 kotlin拦惋,包含多種解法匆浙、解題思路、時間復(fù)雜度厕妖、空間復(fù)雜度分析

  • 劍指 offer 及國內(nèi)外大廠面試題解:在線閱讀

  • LeetCode 系列題解:在線閱讀

  • 最新 Android 10 源碼分析系列文章首尼,了解系統(tǒng)源碼,不僅有助于分析問題,在面試過程中饰恕,對我們也是非常有幫助的挠羔,倉庫持續(xù)更新井仰,歡迎前去查看 Android10-Source-Analysis

  • 整理和翻譯一系列精選國外的技術(shù)文章埋嵌,每篇文章都會有譯者思考部分,對原文的更加深入的解讀俱恶,倉庫持續(xù)更新雹嗦,歡迎前去查看 Technical-Article-Translation

  • 「為互聯(lián)網(wǎng)人而設(shè)計,國內(nèi)國外名站導(dǎo)航」涵括新聞合是、體育了罪、生活、娛樂聪全、設(shè)計泊藕、產(chǎn)品、運營难礼、前端開發(fā)娃圆、Android 開發(fā)等等網(wǎng)址,歡迎前去查看 為互聯(lián)網(wǎng)人而設(shè)計導(dǎo)航網(wǎng)站

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛾茉,一起剝皮案震驚了整個濱河市讼呢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谦炬,老刑警劉巖悦屏,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異键思,居然都是意外死亡础爬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門吼鳞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來看蚜,“玉大人,你說我怎么就攤上這事赖条∈” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵纬乍,是天一觀的道長碱茁。 經(jīng)常有香客問我,道長仿贬,這世上最難降的妖魔是什么纽竣? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上蜓氨,老公的妹妹穿的比我還像新娘聋袋。我一直安慰自己,他們只是感情好穴吹,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布幽勒。 她就那樣靜靜地躺著,像睡著了一般港令。 火紅的嫁衣襯著肌膚如雪啥容。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天顷霹,我揣著相機與錄音咪惠,去河邊找鬼。 笑死淋淀,一個胖子當著我的面吹牛遥昧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播朵纷,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼炭臭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了柴罐?” 一聲冷哼從身側(cè)響起徽缚,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎革屠,沒想到半個月后凿试,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡似芝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年那婉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片党瓮。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡详炬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出寞奸,到底是詐尸還是另有隱情呛谜,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布枪萄,位于F島的核電站隐岛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瓷翻。R本人自食惡果不足惜聚凹,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一割坠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妒牙,春花似錦彼哼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至象浑,卻和暖如春蔫饰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愉豺。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留茫因,地道東北人蚪拦。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像冻押,于是被迫代替她去往敵國和親驰贷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容