在 Java 6 之前,我们在String
. 在 Java 7 中,为什么他们决定使用复制char
数组 - 并降低到线性时间复杂度 - 而类似的东西StringBuilder
正是为此而生的?
5 回答
为什么他们决定在Oracle 错误 #4513622 中讨论:(str)保留字段的子字符串会阻止对象的 GC:
在示例中调用 String.substring 时,不会分配用于存储的新字符数组。它使用原始字符串的字符数组。因此,支持原始字符串的字符数组不能被 GC,直到子字符串的引用也可以被 GC'd。这是一个有意的优化,以防止在常见场景中使用子字符串时过度分配。不幸的是,有问题的代码遇到了原始数组的开销很明显的情况。很难针对两种边缘情况进行优化。空间/尺寸权衡的任何优化通常都很复杂,并且通常可能是特定于平台的。
还有这个注释,指出根据测试,曾经的优化已经变成了悲观:
长期以来,一直在准备和计划从 java.lang.String 中删除 offset 和 count 字段。这两个字段使多个 String 实例能够共享相同的支持字符缓冲区。共享字符缓冲区是旧基准测试的重要优化,但对于当前现实世界的代码和基准测试,实际上最好不共享后备缓冲区。共享 char 数组后备缓冲区只有在大量使用 String.substring 时才会“获胜”。负面影响的情况可能包括解析器和编译器,但当前的测试表明,总体而言,这种变化是有益的。
如果您有一个短寿命的大父字符串的长寿命小子字符串,则支持父字符串的大 char[] 将不符合垃圾收集条件,直到小子字符串超出范围。这意味着子字符串占用的内存可能比人们预期的要多得多。
Java 6 方式唯一一次表现得更好是有人从一个大的父字符串中取出一个大的子字符串,这是一种非常罕见的情况。
显然,他们认为这种变化带来的微小性能成本被旧方法引起的隐藏内存问题所抵消。决定因素是问题被隐藏了,而不是有解决方法。
这将对后缀数组等数据结构的复杂性产生相当大的影响。Java 应该提供一些替代方法来获取原始字符串的一部分。
这只是他们修复一些 JVM 垃圾收集限制的糟糕方式。
在 Java 7 之前,如果我们想避免垃圾回收不工作的问题,我们总是可以复制子字符串而不是保留子字符串引用。这只是对复制构造函数的额外调用:
String smallStr = new String(largeStr.substring(0,2));
但是现在,我们不能再有一个恒定时间的子字符串了。这悲剧。
String
我相信,主要动机是它的最终“共同定位” char[]
。现在它们定位在远处,这是对缓存行的主要惩罚。如果每个人都String
拥有它char[]
,JVM 可以将它们合并在一起,读取速度会快得多。