简易Java(07):substring()
方法在JDK6和JDK7中的异同
看到substring()
,D瓜哥就想起来去年面试的惨痛精力。连续两次被问到substring()
会造成什么问题;第一次被问到时,确实不知道会造成什么问题,面试结束后就查了查资料。但是,没有认真看。没想到接下来的面试又问到,由于没有看,还是没回答上来,结果面试就惨遭失败!
没想到,这次又遇到了substring()
。所以,这篇文章D瓜哥必须好好翻译!
substring()
会造成什么问题,请看参考资料。
另外,需要提前说明一点,本节内容是针对Oracle JDK来说明的,其他JDK的实现也许可能不同。请读者自己查看相关文档以及源代码。
substring(int beginIndex, int endIndex)
方法,在JDK6和JDK7中的实现是不一样的。了解实现的不同之处,可以帮助我们更好地使用这个方法。为了简单起见,下文中用substring()
指代substring(int beginIndex, int endIndex)
。
1、substring()
的作用是啥?
substring(int beginIndex, int endIndex)
方法将返还字符串,这个字符串是原字符串从beginIndex
开始,到endIndex-1
的子串。
输出为:
2、当substring()
被调用时,背后发生了什么?
我们在上一篇文章“图解Java字符串的不可变性 ”讲到,字符串x
是不可变的。所以,当x
被重新赋值为x.substring(1, 3)
时,它指向了一个全新的字符串。如下图所示:

尽管,这个图并不能完全正确地说明堆栈中的确实发生的变化。不过,却可以帮助我们说明,当substring()
被调用时,在JDK6和JDK7下究竟有什么不同。
3、JDK6中的substring()
字符串是通过字符数组的方式来实现的。在JDK6中,String
类包含三个属性:char value[]
、int offset
、int count
,它们分别用于存储真实的字符数组、数组的开始下标以及字符串中的字符数量。
当调用substring()
时,它将创建一个新的字符串对象,但是字符串的值还是指向堆栈中同一个数组。两个字符串对象不同的只是它们的字符数量以及开始下标。如下图所示:

06 | String( int offset, int count, char value[]) { |
12 | public String substring( int beginIndex, int endIndex) { |
14 | return new String(offset + beginIndex, endIndex - beginIndex, value); |
4、JDK6的substring()
导致的问题
如果你有一个非常长的字符串,但是你每次调用substring()
,只需要其中的很小一部分。那么,这将导致一个性能问题,尽管你仅仅需要很小的一部分,但是,却保存了所有的字符。对于JDK6,使用下面的代码,可以解决这个问题,可以使得对象x
确实指向一个真正的子字符串:
1 | x = x.substring(m, n) + "" ; |
5、JDK7中的substring()
上面提到的问题,在JDK7中已经改正。在JDK7中,substring()
方法确实会在堆栈中创建一个新的数组。如下图所示:

D瓜哥注:
这个图片有部分地方是错误的:int count
和int offset
已经不是Oracle JDK7中String
的属性。大家可以从String
的源代码中确认!
06 | String( char value[], int offset, int count) { |
08 | this .value = Arrays.copyOfRange(value, offset, offset+count); |
11 | public String substring( int beginIndex, int endIndex) { |
13 | int subLen = endIndex - beginIndex; |
14 | return ((beginIndex == 0 ) && (endIndex == value.length)) ? this |
15 | : new String(value, beginIndex, subLen); |
《Simple Java》是一本讲解Java面试题的书。讲解也有不少独特之处,为了面试,《简易Java》走起!
参考资料