Pra*_*eep 2 java collections java-9
我正在执行下面的代码片段
System.out.println(List.of(1, 2).getClass());
System.out.println(List.of(1, 2, 3).getClass());
Run Code Online (Sandbox Code Playgroud)
这段代码的输出是;
class java.util.ImmutableCollections$List2
class java.util.ImmutableCollections$ListN
Run Code Online (Sandbox Code Playgroud)
我期待java.util.ImmutableCollections$List3作为第二个语句的输出,因为有一个of()方法需要三个参数,为什么java创建ImmutableCollections$ListN但不是ImmutableCollections$List3?
编辑:这是Java-9问题.在List接口中总共有11个重载的()方法,每个方法从0到10采用可变数量的参数,第11个采用varargs来处理N列表.所以我期待前10个重载方法的List0到List10实现,但它返回带有三个参数的ListN.是的,这是实施细节,但只是想了解更多相关信息.
ListN是通用版本.List2是一个优化的实现.对于具有三个元素的列表,没有这样的优化实现.
目前存在用于列表和集合的*优化版本,具有零个,一个和两个元素.List0,List1,List2,Set0等...
还有一个针对空地图的优化实现Map0,以及包含单个键值对的地图Map1.
有关这些实现如何能够提供性能改进的讨论可以在JDK-8166365中看到.
*请记住,这是一个可能会发生变化的实施细节,实际上很快就会发生变化
拥有多个不同私有实现的主要原因List是节省空间.
考虑将其元素存储在数组中的实现.(这基本上就是ListN这样.)在Hotspot(带有压缩对象指针的64位,每4个字节)中,每个对象需要一个12字节的头.该ListN对象有一个包含该数组的字段,总共16个字节.数组是一个单独的对象,因此它有另一个12字节的标题加上4字节的长度.那是另外16个字节,不包括存储的任何实际元素.如果我们存储两个元素,它们需要8个字节.这使得总共40个字节用于存储双元素列表.这是相当多的开销!
如果我们要在字段而不是数组中存储小列表的元素,那么该对象将有一个标题(12个字节)加上两个字段(8个字节),总共20个字节 - 一半大小.对于小型列表,在List对象本身的字段中存储元素而不是在数组中存在相当大的节省,数组是一个单独的对象.这就是旧的List2实现所做的.它最近被List12实现取代,它可以在字段中存储一个或两个元素的列表.
现在,在API中有12个重载List.of()方法:0到10个固定args加varargs.不应该有相应的List0通过List10和ListN实现吗?
可能会有,但不一定必须如此.这些实现的早期原型具有与API相关联的优化小列表实现.因此,零个,一个,和两个固定ARG of()方法产生的实例List0,List1以及List2,和可变参数List.of()方法创建的一个实例ListN.这是相当简单的,但它是相当严格的.我们希望能够随意添加,删除或重新安排实现.更改API要困难得多,因为我们必须保持兼容.因此,我们决定将事物分离,以便API中的参数数量在很大程度上独立于下面实例化的实现.
在JDK 9中,我们最终得到了API中的12个重载,但只有4个实现:基于字段的实现包含0,1和2个元素,以及一个包含任意数字的基于数组的实现.为什么不添加更多基于字段的实现?收益递减,代码臃肿.大多数列表都有很少的元素,随着元素数量的增加,列表出现时会出现指数下降.与基于阵列的实现相比,节省的空间相对较小.然后是维护所有这些额外实现的问题.它们必须直接输入源代码(庞大)或者我们切换到代码生成方案(复杂).似乎都不合理.
我们的初创性能大师Claes Redestad做了一些测量,发现有更少的列表实现加速.原因是变形调度.简而言之,如果JVM正在编译虚拟调用站点的代码,并且它可以确定只调用了一个或两个不同的实现,那么它可以很好地优化它.但是如果有许多不同的实现可以调用,它必须通过一个较慢的路径.(有关Black Magic的详细信息,请参阅此文章.)
对于列表实现,事实证明我们可以通过更少的实现来实现而不会损失太多空间.在List1和List2实施方式可以组合成一个双场List12实现方式中,具有第二场被空如果只有一个元素是.我们只需要一个零长度列表,因为它是不可变的!对于零长度列表,我们可以摆脱List0使用ListN零长度数组.它比旧List0实例更大,但我们并不关心,因为它们只有一个.
这些更改刚刚进入JDK 11主线.由于API与实现完全分离,因此不存在兼容性问题.
未来的增强功能还有其他可能性.一个潜在的优化是将阵列融合到对象的末端,因此对象具有固定部分和可变长度部分.这将避免需要数组对象的头,它可能会改善引用的局部性.另一个潜在的优化是价值类型.使用值类型,可以完全避免堆分配,至少对于小列表.当然,这都是高度投机的.但是如果JVM中出现了新功能,我们可以在实现中利用它们,因为它们完全隐藏在API之后.
| 归档时间: |
|
| 查看次数: |
1001 次 |
| 最近记录: |