要開始寫新的 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ū)間表達式 ( ..
岸蜗、 downTo
、 until
) 除了創(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)建 IntRange
、 IntProgression
對象,會占用更多的內(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ù)組
withIndex
和 indices
遍歷數(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)站