Menu
What are you looking for?
  • AiTecms District, Guangzhou, China

灵魂拷问:Java 的 substring() 是如何工作的?

Source:Author:admin Addtime:2019/12/21

在逛 programcreek 的时分,我发现了一些小而精悍的主题。比如说:java 的 k8.com凯发真人substring 办法是怎样作业的?像这类魂灵拷问的主题,十分值得深化地研讨一下。

别的,我想要告知咱们的是,研讨的进程十分的风趣,就好像在迷宫里探宝相同,起先有些手足无措,但经过一番用心的探索后,不但会找到瑰宝,还会有一种恍然大悟的感觉,十分棒。

关于绝大多数的初级程序员或者说不注重“内功”的老鸟来说,往往停留在“知其然不知其所以然”的层面上——会用,但要说底层的原理,可就只能挠犯难双手一摊一张问号脸了。

很长一段时间内,我也一向处于这种层面上。但我决议改动了,因为“内功”就好像是在打地基,只要把地基打好了,才干盖起经得住检测的高楼大厦。借此机会,我就和咱们一同,对“java 的 substring 是怎样作业的”进行一次深化地研讨。留意了,预备打怪晋级了!

sub 是 subtract 的缩写,因而 substring 的字面意思便是“把字符串做个减法”。这样一剖析,是不是感觉办法的命名仍是蛮有考究的?

substring 的完好写法是 substring。该办法回来一个新的字符串,介于原有字符串的开端下标 beginindex 和结束下标 endindex-1 之间。

string cmower = "缄默沉静王二,一枚风趣的程序员";
cmower = cmower.substring;
system.out.println;



程序输出的成果为:

缄默沉静王二

为什么呢?我来简略解释一下。

java 的下标都是从 0 开端编号的,这和咱们往常日子中从 1 开端编号的习气不同。java 这样做的原因如下:

java 是根据 c 言语完成的,而 c 言语的下标是从 0 开端的——这听起来好像是一句废话。真实的原因是下标并不是下标,在指针言语中,它实际上是一个偏移量,间隔开端方位的一个偏移量。第一个元素在最初,因而它的偏移量就为 0。

此外,还有别的一种说法。前期的计算机资源比较匮乏,0 作为开端下标相比较于 1 作为开端下标,编译的功率更高。

知道了这层原因后,再来看上面这段代码,就会恍然大悟。关于“缄默沉静王二,一枚风趣的程序员”这串字符来说,“沉”的下标为 0,“默”的下标为 1,“王”的下标为 2,“二”的下标为 3,所以 cmower.substring 回来的字符串是“缄默沉静王二”——包含开端下标但不包含结束下标。

在此之前,咱们现已了解到:[字符串是不可变的],因而当调用 substring 办法的时分,回来的其实是一个新的字符串。那么变量 cmower 的地址引证就会发作如下图所示的改动。

为了证明上图是完全正确的,咱们来看一下 jdk 7 中 substring 的源码。

public string {
 //check boundary
 this.value = arrays.copyofrange;
}

public string substring {
 //check boundary
 int sublen = endindex - beginindex;
 return new string;
}










能够看得出,substring 经过 new string 回来了一个新的字符串目标,在创立新的目标时经过 arrays.copyofrange 仿制了一个新的字符数组。

但 jdk 6 就有所不同。提到 jdk 6,或许有些读者表明不服,jdk 6?什么时代了,jdk 13 都出来了好不好?但我想告知咱们的是,对比着剖析 jdk 的源码,对学习大有裨益。

不是有那么一句话嘛,要想了解一个成功人士,不能只重视他发迹今后的事,更要重视他之前做了什么。

就请随我来,看看 jdk 6 中的 substring 的源码吧。

//jdk 6
string {
 this.value = value;
 this.offset = offset;
 this.count = count;
}

public string substring {
 //check boundary
 return new string;
}











substring 办法自身和 jdk 7 并没有很大的不同,都经过 new string 回来了一个新的字符串目标。可是 string 这个结构函数有很大的不同,jdk 6 仅仅简略地更改了一下两个特点的值,value 并没有变。

ps:value 是真实存储字符的数组,offset 是数组中第一个元素的下标,count 是数组中字符的个数。

这意味着什么呢?

调用 substring 的时分尽管创立了新的字符串,但字符串的值依然指向的是内存中的同一个数组,如下图所示。

看了 jdk 6 和 jdk 7 源码之后,咱们或许发生这样一个疑问:为什么 jdk 7 要做出改动呢?咱们共用同一个字符串数组不是挺好的嘛,省得占用新的内存空间。事实上呢?

假如有一个很长很长的字符串,能够绕地球一周,当咱们需求调用 substring 截取其间很小一段字符串时,就有或许导致功能问题。因为这一小段字符串引证了整个很长很长的字符数组,就导致很长很长的这个字符数组无法被收回,内存一向被占用着,就有或许引发内存走漏。

ps:内存走漏是指因为忽略或过错形成程序未能开释现已不再运用的内存。

那 jdk 7 呈现之前,这个危险怎样应对呢?答案如下。

cmower = cmower.substring + "";

为什么,为什么,为什么,多一个 “+ ""” 就能处理内存走漏的问题?有些读者或许不太信任,我来带咱们剖析一下。

首要呢,咱们经过 jad 对字节码反编译一下,上面这行代码就变成了如下内容。

 cmower = ))).tostring;

“+”号操作符就相当于一个语法糖,加上空的字符串后,会被 jdk 转化为 stringbuilder 目标,该目标在处理字符串的时分会生成新的字符数组,所以 cmower = cmower.substring + ""; 这行代码履行后,cmower 就指向了和 substring 调用之前不同的字符数组。

ps:假如不明白“+”号操作符的作业原理,请查阅我之前写的文章《羞,java 字符串拼接竟然有这么多姿态》,这儿就不再赘述,以免被老读者捶。

总结一下,jdk 7 和 jdk 6 的 substring 办法自身并没有多大的改动,但 string 类的结构函数有了很大的差异,jdk 7 会从头仿制一份字符数组,而 jdk 6 不会,因而 jdk 6 在履行比较长的字符串 substring 时或许会引发内存走漏的问题。