Java7字符串-子字符串复杂性

Java7字符串-子字符串复杂性,java,java-7,Java,Java 7,在Java6之前,我们在String上有一个常量时间子字符串。在Java 7中,为什么他们决定复制char数组,并降低线性时间复杂度,而StringBuilder正是这样做的呢?如果您有一个长寿命的小子字符串,它是一个短命的大父字符串,即大char[]在小的子字符串移出范围之前,支持父字符串将不符合垃圾收集的条件。这意味着子字符串可能比人们预期的占用更多的内存 Java 6方式的唯一一次性能显著提高是当有人从一个大的父字符串中获取一个大的子字符串时,这是一种非常罕见的情况 很明显,他们认为这种改

在Java6之前,我们在
String
上有一个常量时间子字符串。在Java 7中,为什么他们决定复制
char
数组,并降低线性时间复杂度,而
StringBuilder
正是这样做的呢?

如果您有一个长寿命的小子字符串,它是一个短命的大父字符串,即大char[]在小的子字符串移出范围之前,支持父字符串将不符合垃圾收集的条件。这意味着子字符串可能比人们预期的占用更多的内存

Java 6方式的唯一一次性能显著提高是当有人从一个大的父字符串中获取一个大的子字符串时,这是一种非常罕见的情况


很明显,他们认为这种改变的微小性能成本被旧方法引起的隐藏内存问题所抵消。决定因素是问题被隐藏了,而不是有解决办法。

他们决定的原因在以下章节中讨论:

如本例所示调用String.substring时,不会为存储分配新的字符数组。它使用原始字符串的字符数组。因此,在子字符串的引用也可以被GC'd之前,支持原始字符串的字符数组不能被GC'd。这是一种有意的优化,以防止在常见场景中使用子字符串时分配过多。不幸的是,有问题的代码遇到了原始数组开销明显的情况。在两种情况下都很难进行优化。空间/尺寸权衡的任何优化通常都是复杂的,并且往往是特定于平台的

还有一点值得注意的是,根据测试,曾经的优化已经变成了悲观:

为了从java.lang.String中删除偏移量和计数字段,已经进行了很长时间的准备和计划。这两个字段允许多个字符串实例共享同一备份字符缓冲区。共享字符缓冲区对于旧的基准测试来说是一个重要的优化,但对于当前的真实代码和基准测试,实际上最好不要共享备份缓冲区。共享字符数组备份缓冲区仅“赢”,大量使用String.substring。负面影响的情况可能包括解析器和编译器,但当前的测试表明,总体而言,这种更改是有益的


这将对后缀数组等数据结构的复杂性产生相当大的影响。Java应该提供一些替代方法来获取原始字符串的一部分。

这只是他们修复一些JVM垃圾收集限制的蹩脚方法

在Java7之前,如果我们想避免垃圾收集不工作的问题,我们总是可以复制子字符串,而不是保留子字符串引用。这只是对复制构造函数的额外调用:

String smallStr = new String(largeStr.substring(0,2));

但是现在,我们不能再有一个恒定的时间子串。这真是一场灾难。

我认为,主要的动机是
字符串及其
字符[]
的最终“共定位”。现在,它们位于一段距离内,这是缓存线的主要损失。如果每个
字符串
都拥有自己的
字符[]
,JVM可以将它们合并在一起,读取速度会更快。

为了避免字符串长度较小,可以防止对任意大的
字符[]
进行垃圾收集。使用
StringBuilder
可以解决这个问题,不是吗?使用
StringBuilder
可以让您在意识到问题存在后解决问题。但它不能修复现有代码中的内存泄漏。此更改修复了现有代码中的内存泄漏,并且由于缓冲区副本通常受硬件支持,因此不会为一个虚拟内存页中的任何子字符串花费线性时间。trim()从一个大的父字符串中获取一个大的子字符串,并且一直在使用。因此,算法的性能会受到损害(较差..)设计决策是一种常见现象,并非罕见。这是完全正确的。许多类型的程序都受益于共享子字符串的使用。编译器和解析器很好地说明了最受伤害的操作类型:但损害远远超出了这些特定类型的程序。有人知道有任何第三方库/代码具有复制“旧”子字符串行为的CharSequence自定义实现(或类似实现)吗?我经常需要处理类似CSV的大型文件(500+MB),每当我分析它们时,我发现至少有10%的处理时间浪费在对Arrays.copyOfRange()的调用上。@SimonBerthiaume性能错误首先是创建
String
实例,它甚至在调用
子字符串之前就已经执行了不必要的复制操作。由于每个
字符集解码器
,包括封装在
读取器
中的解码器,都在
字符缓冲区
上运行,这就是您的出发点。它已经是解决方案了,因为它实现了
CharSequence
,所以您可以将它传递给诸如regex模式匹配引擎之类的工具,并且可以自由复制
子序列
切片
操作。您只需要创建最终匹配结果字符串。即使是简单的
java.util.Scanner
也是如此现在我们可以使用
子序列(startIndex:Int,endIndex:Int):CharSequence