新版Java为什么要修改substring的实现
作者:网络转载 发布时间:[ 2015/1/28 14:05:59 ] 推荐标签:Java 软件开发
为何要修改substring
原来的substring在安全上没有问题,而且性能很好,又能共享内部数组节约内存。这么看来,好像并没有什么缺点。那为什么要放弃性能更好的实现方式,而采用性能差很多的数组拷贝的方式呢?难道是Oracle的工程师脑抽才会对substring做出这样的修改吗?
当然不是,原来的方法比新的好只是表面现象,因为虽然性能好,但是有一个严重的问题,那是有可能会导致内存泄漏。看一个例子,假设一个方法从某个地方(文件、数据库或网络)取得了一个很长的字符串,然后对其进行解析并提取其中的一小段内容,这种情况经常发生在网页抓取或进行日志分析的时候。下面是示例代码。
String aLongString = ...; // a very long string
String aPart = data.substring(20, 40);
return aPart;
在这里aLongString只是临时的,真正有用的是aPart,其长度只有20个字符,但是它的内部数组却是从aLongString那里共享的,因此虽然aLongString本身可以被回收,但它的内部数组却不能(如下图)。这导致了内存泄漏。如果一个程序中这种情况经常发生有可能会导致严重的后果,如内存溢出,或性能下降。

新的实现虽然损失了性能,而且浪费了一些存储空间,但却保证了字符串的内部数组可以和字符串对象一起被回收,从而防止发生内存泄漏,因此新的substring比原来的更健壮。
实际上前面所说的那个包私有的构造方法在1.7中已经被标记为Deprecated,并且实现也修改为直接调用“public String(char[], int, int)”。到了1.8这个构造方法被删除了。取而代之的是从1.7开始,增加了另一个共享版的构造方法,这个方法也是包私有的:
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
第二个参数目前没有用到,始终为true,仅仅是为了和另一个公有构造方法“String(char[])”相区别才增加了这么一个参数。这个构造方法用来创建一个和原字符串一模一样的字符串,而不是像以前一样可以创建原字符串的一个子串。在这种情况下,共享数组不会导致内存泄漏问题,只是其用处大打折扣,因为只有很少的情况需要创建一个和原字符串一模一样的字符串,多数情况只需使用原字符串即可。这像构造方法“String(String)”一样,应该很少有人会使用它来创建字符串吧。
总结
原来的substring性能好,但在一些情况下却可能导致严重的内存泄漏。新的substring没有内存泄漏的隐患,因此健壮性更好,但却是通过牺牲性能换来的。
两种实现孰优孰劣还真不好说,因为在大多数情况下都不会遇到所谓的严重内存泄漏的情况,因此大部分时候新的substring都不如原来的好。但对一个运行库来说,健壮性可能更重要一些,毕竟它需要适用于任何可能遇到的情况。

sales@spasvo.com