新版Java为什么要修改substring的实现
Java字符串的截取操作可以通过substring来完成。有意思的是,这个方法从jdk1.0开始,一直到1.6都没有变化,但到了1.7实现方式却发生了改变。你可能会认为之所以要对一个成熟且稳定的方法做修改,一定是因为新的实现更好、效率更高吧?然而正好相反,修改后的substring的效率变低了,并且占用了更多的内存,无论是从时间上还是空间上都比不上原有的实现。下面我们来做一个比较,看看到底哪一个更好,以及为什么新版Java中要对其进行修改。
原有实现
我们首先来看看原来的substring方法。前面是对参数进行检查,重点是最后一句:
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
这里通过调用下面这个构造方法来创建一个新的字符串:
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
我们知道,Java的字符串实际上是用一个字符数组来实现的,这个构造方法通过复用字符数组value,省去了数组拷贝的开销,仅通过3个赋值语句就创建了一个新的字符串对象。从注释也可以看出这个构造方法的意图就是为了提升性能。
新的实现
我们再来看看1.7中新的substring实现。前面一堆还是参数检查,直接看最后一句:
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
与原来的差不多,但是请注意,这次调用的是另一个构造方法:
public String(char[], int, int)
这个公有的构造方法和前面那个很相似(那个是包私有的),从方法签名上看区别仅仅是参数顺序不同。不过这只是表面现象,它们的内部实现却是完全不同的,这个公有的构造方法不会复用char[]数组,而是将其拷贝到一个新数组,从而创建一个新字符串。
this.value = Arrays.copyOfRange(value, offset, offset+count);
对公有的构造方法来说,必须采用这种方式,如果仍然采用复用数组的方法,就会发生安全性问题,别人就可以对字符串中的字符进行任意的修改。后面会对此进行分析。
复用字符数组有没有安全隐患
Java的字符串是不可变的,原因是作为字符串底层实现的字符数组是私有的,从外面无法访问。另一方面,String类的每一个可以创建新字符串的公有方法(构造方法、valueOf等),如果其接受一个字符数组作为参数,就会对该数组执行拷贝操作,这就进一步保证了只有String对象才会持有它的字符数组,因此断绝了从外部修改数组的一切可能。
如果不这么做就会带来问题,字符串的不可变性也就不复存在了。比如下面这个假想的程序:
char[] arr = new char[] {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
String s = new String(0, arr.length, arr); // "hello world"
arr[0] = 'a'; // replace the first character with 'a'
System.out.println(s); // aello world
如果构造方法没有对arr进行拷贝,那么其他人就可以在字符串外部修改该数组,由于它们引用的是同一个数组,因此对arr的修改就相当于修改了字符串。(可以通过反射来真正地实现这个假想的程序)
还有一些方法,比如原来的substring方法,它们没有进行数组拷贝,而是直接复用另一个字符串的内部数组。这样做会导致安全问题吗?答案是不会,因为所有这些方法所执行的操作都是私有操作或包私有操作,属于内部实现,因此只要不对外暴露这些操作的接口就仍然是安全的。
例如对substring来说,由于无论是原字符串还是新字符串,其value数组本身都是String对象的私有属性,从外部是无法访问的,因此对两个字符串来说都很安全。
为何要修改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都不如原来的好。但对一个运行库来说,健壮性可能更重要一些,毕竟它需要适用于任何可能遇到的情况。
新版Java为什么要修改substring的实现的更多相关文章
- java实现批量修改指定文件夹下所有后缀名的文件为另外后缀名的代码
java实现批量修改指定文件夹下所有后缀名的文件为另外后缀名的代码 作者:Vashon package com.ywx.batchrename; import java.io.File; import ...
- JAVA中字符串函数subString的用法小结
本篇文章主要是对JAVA中字符串函数subString的用法进行了详细的介绍,需要的朋友可以过来参考下,希望对大家有所帮助 String str; str=str.substring(int begi ...
- Hadoop使用Java进行文件修改删除操作
Hadoop使用Java进行文件修改删除操作 学习了:http://blog.csdn.net/menghuannvxia/article/details/44651061 学习了:http://bl ...
- Java使用BufferedImage修改图片内容
1.修改图片的架包 <dependency> <groupId>commons-io</groupId> <artifactId>commons-io& ...
- 阿里出品的最新版 Java 开发手册,嵩山版,扫地僧
说起嵩山,我就想起乔峰,想起慕容复,以及他们两位老爹在少林寺大战的场景.当然了,最令我印象深刻的就是那位默默无闻,却一鸣惊人的扫地僧啊.这次,阿里出品的嵩山版 Java 开发手册的封面就有一个扫地僧, ...
- 在Linux上安装最新版java的JDK
之前写过一篇关于MC建服的文章(http://www.cnblogs.com/apollospotatolikett/p/6149042.html),文章中使用的JDK不是最新的版本,当时没有细说如何 ...
- Java for LeetCode 030 Substring with Concatenation of All Words【HARD】
You are given a string, s, and a list of words, words, that are all of the same length. Find all sta ...
- Java实现批量修改文件名称
import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; /** ...
- Java反射之修改常量值
1. 通过反射修改常量的值 package com.blueStarWei.invoke; import java.lang.reflect.Field; public class ModifyFin ...
随机推荐
- HSTS的来龙去脉
前言 安全经常说“云.管.端”,“管”指的是管道,传输过程中的安全.为了确保信息在网络传输层的安全,现在很多网站都开启了HTTPS,也就是HTTP+TLS,在传输过程中对信息进行加密.HTTPS使用了 ...
- HDU.1689 Just a Hook (线段树 区间替换 区间总和)
HDU.1689 Just a Hook (线段树 区间替换 区间总和) 题意分析 一开始叶子节点均为1,操作为将[L,R]区间全部替换成C,求总区间[1,N]和 线段树维护区间和 . 建树的时候初始 ...
- 洛谷 P1023 税收与补贴问题 (2000NOIP提高组)
洛谷 P1023 税收与补贴问题 (2000NOIP提高组) 题意分析 一开始没理解题意.啰啰嗦嗦一大堆.看了别人的题解才明白啥意思. 对于样例来说,简而言之: 首先可以根据题目推算出来 28 130 ...
- bzoj2054: 疯狂的馒头(并查集)
每个区间只被覆盖一次,求每个点被哪种区间覆盖或者某个区间是否已经被覆盖过都可以用并查集做. 做法:每个点都指向当前被覆盖区间的右端点+1的位置,某个点的下一个没被覆盖的点是gf(i),同理如果某个区间 ...
- 解题:AHOI 2005 航线规划
题面 这种不断删边的首先肯定想到时光倒流啊=.= 在最后剩下的连通图上跑出一棵搜索树,先将边权都赋为$1$,那么两点间的关键航线就是链上边权和,而每加入一条非树边$u,v$都会使得$u,v$链上的边的 ...
- 排座位&&Little Elephant And Permutation——排列dp的处理
排列的问题,就是要把序列排个序,使之达到某种最优值或者统计方案数 dp可以解决部分排列问题. 通常的解决方案是,按照编号(优先级)排序决策,从左到右决策两种. 这里主要是第一个. 排座位• 有
- 【hdu4035】Maze
Portal --> hdu4035 Solution 讲道理不是很懂为啥概d那么喜欢走迷宫qwq (推式子推的很爽的一题?) 首先大力dp列式子 用\(f[i]\)表示从\(i\)到离开的期望 ...
- java如何优雅的实现时间控制
前言:最近小王同学又遇到了一个需求:线上的业务运行了一段时间,后来随着使用人数增多,出现了一个问题是这样的,一个订单会重复创建几次,导致数据库里出现了很多垃圾数据.在测试同学的不断测试下,发现问题出在 ...
- 【数据结构】【平衡树】treap
之前写treap的传送门 之前写的那个太毒瘤了,这次放一个更毒瘤的指针版上来 #include<cstdio> #include<iostream> #define rg re ...
- 关于OpenCV的stitching使用
配置环境:VS2010+OpenCV2.4.9 为了使用OpenCV实现图像拼接头痛了好长时间,一直都没时间做,今天下定决心去实现基本的图像拼接. 首先,看一看使用OpenCV进行拼接的方法 基本都是 ...