携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的15天,点击查看活动详情
为了活动小家电,接着上篇搞!
JVM 中的缓存字符串和盒装原始对象
在 JVM 中,如果在一个虚拟机中有多个代码处理同一个字符串,则重用现有的字符串。
fun main() {
val str1 = "hello world"
val str2 = "hello world"
val str3 = StringBuilder("hello world").toString()
println(str1 == str2) // true, 等价比较
println(str1 === str2) // true, 内存地址比较
println(str2 === str3) // false, 创建新对象,分配新内存
println(str2 === str3.intern()) // true, 从 JVM 字符串池中获取一个对象
}
默认情况下,Integer 和 Long 等盒装原语也会缓存 -128 到 127 的范围。
fun main() {
val i1: Int? = 1
val i2: Int? = 1
val i3: Int? = 1000
val i4: Int? = 1000
println(i1==i2) // true, 比较值
println(i1===i2) // true, 比较内存地址
println(i3==i4) // true, 比较值
println(i3===i4) // false,没有缓存所以比较内存地址
}
它不在协程范围内缓存。
让我们将上面的示例代码包装在一个简单的 runBlocking 范围内。
fun main() = runBlocking {
val str1 = "hello world"
val str2 = "hello world"
val str3 = StringBuilder("hello world").toString()
println(str1 == str2) // true
println(str1 === str2) // true
println(str2 === str3) // false
println(str2 === str3.intern()) // true
}
String 对于字符串,即使在协程范围内,它也会显示与现有示例代码相同的结果。但是让我们看一下以下用于缓存盒装原语的示例代码。
fun main() = runBlocking {
val i1: Int? = 1
val i2: Int? = 1
val i3: Int? = 1000
val i4: Int? = 1000
println(i1==i2) // true
println(i1===i2) // false, 不缓存
println(i3==i4) // true
println(i3===i4) // false
}
}
可以看出(i1 === i2)的比较返回false。我可以看到它在正常函数中执行时被缓存了,但是为什么在协程范围内结果不同?
我反编译了那些代码。
红框标注的部分是需要注意的部分。左边指的是编译后缓存在 JVM 中的值,右边指的是 Boxing.boxInt(…) 。在这种情况下,内部创建了一个新对象并分配了新的内存,因此引用被划分。
我不知道为什么编译器的行为在协程范围内有所不同。但是,在某些情况下,盒装原始数据类型不会被缓存。