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