本日主題 - Item 47: 避免非必要的物件創建
物件的建立總是需要某些代價的,有時可能很昂貴。這就是為什麼避免不必要的物件建立可以是一個重要的優化方法。這可以在許多層面上完成。例如,在 JVM 中,保證了一個同樣字串在同一虛擬機上運行時都是唯一的,有一個字串池。這也間接的讓 ==
===
是相同的。
val str1 = "Lorem ipsum dolor sit amet"
val str2 = "Lorem ipsum dolor sit amet"
print(str1 == str2) // true
print(str1 === str2) // true
每次我們使用建構子時,我們都會建立一個新的物件,但當我們使用工廠函數時,這不一定是真的,因為它們可以有一個快取。最簡單的情況是工廠函數始終返回相同的物件。例如,這是 stdLib 中的 emptyList 的實現:
fun <T> emptyList(): List<T> = EmptyList
有時我們有一組物件,我們根據參數返回其中之一。例如,Kotlin 協程中的 Dispatchers.Default 擁有一個執行緖池;每當我們使用預設調度器開始任何操作時,他會在池中找到未被使用的執行緒來啟動。對於帶參數的工廠函數,也可以進行快取。在這種情況下,我們可能會在 Map 中保留我們的物件:
private val connections: MutableMap<String, Connection> =
mutableMapOf<String, Connection>()
fun getConnection(host: String) =
connections.getOrPut(host) { createConnection(host) }
這種方式可以有效地避免重複的計算,並提高程式的性能。不過要注意這是用空間換取時間,需要一些策略來控制快取的數量。這是常見的 pattern, 因此,我們可引入快取的函式庫
Caffeine 是 Java Cache 中的後起之秀,提供了更高的性能和更多的功能。
val cache = Caffeine.newBuilder()
.maximumSize(1000) // 設定最大容量
.expireAfterWrite(5, TimeUnit.MINUTES) // 設定寫入後5分鐘到期
.build<String, String>()
cache.put("key1", "value1")
val value = cache.getIfPresent("key1")
val value = cache.get("key2") { computeValueForKey(it) }
Caffeine 還提供了更多高階功能,例如大小策略、刷新策略、監聽器等。
Aedile 是一個針對 Kotlin 協程的快取庫,主要封裝了 Caffeine。它提供了對 Kotlin 協程的原生支援,可以更直接與 suspend function 整合。
以下是如何使用 Aedile 來快取 suspend function 的結果:
val cache = caffeineBuilder<String, FruityVice> {
expireAfterWrite = 5.toDuration(DurationUnit.MINUTES)
}.build { findByName(it) }
// expensive call
private suspend fun findByName(name: String) =
suspend fun findByNameWithCache(name: String) =
cache.get(name)
當你第一次調用 findByNameWithCache 時,它會實際調用 findByName 並將結果儲存在快取中。之後的調用會直接從快取中獲取結果,除非該結果已超過時間(5分鐘)被清出。
這是 mobile 開發者推薦的另一個快取函式庫叫 store, 概念會是差不多,大家也是可以參考看看。
https://mobilenativefoundation.github.io/Store/
TOMBOY 舞台真的很炸