答案 0 :(得分:17)
听起来好像packed-switch
等同于Java的tableswitch
,sparse-switch
等同于lookupswitch
。
packed-switch
使用简单的跳转表,其格式为low + n
,其中low
是case
标签中最低的测试值,n
}是switch
的输入。每个索引处的值表示每个case
的字节码偏移量。找到正确的跳转地址是一个恒定时间操作。
sparse-switch
使用键值对的排序列表,其中每个键是来自case
标签的测试值,值是跳转偏移量。找到lookupswitch
的正确跳转目标需要对键进行二进制搜索,因此它是对数时间操作。
编译器将选择使用哪个。如果密钥往往聚集在一起或打包紧密结合在一起,则可以有效地发出packed-switch
(或者,在Java术语中,tableswitch
)。但是如果键是稀疏,并且值的范围(high - low + 1
)很大,那么使用跳转表将需要大块字节码,因为该范围内的所有值都必须存在在跳转表中,无论是否有相应的case
标签。在这些情况下,编译器将发出sparse-switch
(lookupswitch
)。
有趣的是,Dalvik工程师选择以描述应该使用它们的关键发行版的方式命名这些操作码,而Java工程师选择描述字节码操作数类似的概念数据结构的名称。
让我们看一些例子。考虑以下Java代码,它将产生tableswitch
(当转换为Dalvik时,packed-switch
):
static String packedSwitch(final int n) {
switch (n) {
case 5:
return "Five";
case 3:
return "Three";
case 1:
return "One";
default:
return "Other";
}
}
从概念上讲,packed-switch
操作码的有效负载看起来像这样:
如您所见,它相当紧凑。五个插槽中有三个指向实际的case
目标,其余两个指向跳转到default
目标。但是如果我们的测试值更加分散呢?
static String sparseSwitch(final int n) {
switch (n) {
case 500:
return "Five Hundred";
case 300:
return "Three Hundred";
case 100:
return "One Hundred";
default:
return "Other";
}
}
如果编译器尝试将其作为packed-switch
发出,则有效负载将如下所示:
请注意,几百个插槽中只有三个实际指向原始代码中的case
个标签。其余的只是填补跳转表。空间效率不是很高,是吗?这就是编译器发出sparse-switch
的原因,它为这个特定的例子提供了更加紧凑的字节码占用空间:
现在,这更合理,你不觉得吗?然而,缺点是,不是基于输入确切地知道要跳转到哪个索引,我们必须在表上执行二进制搜索,直到找到匹配的测试值。尽管效果具有对数曲线,但开关越大,对性能的影响就越显着。