自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

靈魂拷問(wèn):Java 的 substring() 是如何工作的?

開(kāi)發(fā) 后端
在逛 programcreek 的時(shí)候,我發(fā)現(xiàn)了一些小而精悍的主題。比如說(shuō):Java 的 substring() 方法是如何工作的?像這類靈魂拷問(wèn)的主題,非常值得深入地研究一下。

 在逛 programcreek 的時(shí)候,我發(fā)現(xiàn)了一些小而精悍的主題。比如說(shuō):Java 的 substring() 方法是如何工作的?像這類靈魂拷問(wèn)的主題,非常值得深入地研究一下。

[[282826]]

另外,我想要告訴大家的是,研究的過(guò)程非常的有趣,就好像在迷宮里探寶一樣,起初有些不知所措,但經(jīng)過(guò)一番用心的摸索后,不但會(huì)找到寶藏,還會(huì)有一種茅塞頓開(kāi)的感覺(jué),非常棒。

對(duì)于絕大多數(shù)的初級(jí)程序員或者說(shuō)不重視“內(nèi)功”的老鳥(niǎo)來(lái)說(shuō),往往停留在“知其然不知其所以然”的層面上——會(huì)用,但要說(shuō)底層的原理,可就只能撓撓頭雙手一攤一張問(wèn)號(hào)臉了。

很長(zhǎng)一段時(shí)間內(nèi),我也一直處于這種層面上。但我決定改變了,因?yàn)?ldquo;內(nèi)功”就好像是在打地基,只有把地基打好了,才能蓋起經(jīng)得住考驗(yàn)的高樓大廈。借此機(jī)會(huì),我就和大家一起,對(duì)“Java 的 substring() 是如何工作的”進(jìn)行一次深入地研究。注意了,準(zhǔn)備打怪升級(jí)了!

1、substring() 是干嘛的

sub是 subtract 的縮寫(xiě),因此 substring 的字面意思就是“把字符串做個(gè)減法”。這樣一分析,是不是感覺(jué)方法的命名還是蠻有講究的?

substring() 的完整寫(xiě)法是 substring(int beginIndex, int endIndex)。該方法返回一個(gè)新的字符串,介于原有字符串的起始下標(biāo) beginIndex 和結(jié)尾下標(biāo) endIndex-1 之間。

 

  1. String cmower = "沉默王二,一枚有趣的程序員"
  2. cmower = cmower.substring(0, 4); 
  3. System.out.println(cmower); 

程序輸出的結(jié)果為:

沉默王二

為什么呢?我來(lái)簡(jiǎn)單解釋一下。

Java 的下標(biāo)都是從 0 開(kāi)始編號(hào)的(我不確定有沒(méi)有從 1 開(kāi)始的編程語(yǔ)言),這和我們平常生活中從 1 開(kāi)始編號(hào)的習(xí)慣不同。Java 這樣做的原因如下:

Java 是基于 C 語(yǔ)言實(shí)現(xiàn)的,而 C 語(yǔ)言的下標(biāo)是從 0 開(kāi)始的——這聽(tīng)起來(lái)好像是一句廢話。真正的原因是下標(biāo)并不是下標(biāo),在指針(C)語(yǔ)言中,它實(shí)際上是一個(gè)偏移量,距離開(kāi)始位置的一個(gè)偏移量。第一個(gè)元素在開(kāi)頭,因此它的偏移量就為 0。

此外,還有另外一種說(shuō)法。早期的計(jì)算機(jī)資源比較匱乏,0 作為起始下標(biāo)相比較于 1 作為起始下標(biāo),編譯的效率更高。

知道了這層原因后,再來(lái)看上面這段代碼,就會(huì)豁然開(kāi)朗。對(duì)于“沉默王二,一枚有趣的程序員”這串字符來(lái)說(shuō),“沉”的下標(biāo)為 0,“默”的下標(biāo)為 1,“王”的下標(biāo)為 2,“二”的下標(biāo)為 3,所以 cmower.substring(0, 4) 返回的字符串是“沉默王二”——包括起始下標(biāo)但不包括結(jié)尾下標(biāo)。

2、substring() 在被調(diào)用的時(shí)候究竟發(fā)生了什么?

在此之前,我們已經(jīng)了解到:字符串是不可變的,因此當(dāng)調(diào)用 substring() 方法的時(shí)候,返回的其實(shí)是一個(gè)新的字符串。那么變量 cmower 的地址引用就會(huì)發(fā)生如下圖所示的變化。

 

 

 

 

為了證明上圖是完全正確的,我們來(lái)看一下 JDK 7 中 substring() 的源碼。

 

  1. public String(char value[], int offset, int count) { 
  2.     //check boundary 
  3.     this.value = Arrays.copyOfRange(value, offset, offset + count); 
  4.   
  5. public String substring(int beginIndex, int endIndex) { 
  6.     //check boundary 
  7.     int subLen = endIndex - beginIndex; 
  8.     return new String(value, beginIndex, subLen); 

可以看得出,substring() 通過(guò) new String() 返回了一個(gè)新的字符串對(duì)象,在創(chuàng)建新的對(duì)象時(shí)通過(guò) Arrays.copyOfRange() 復(fù)制了一個(gè)新的字符數(shù)組。

但 JDK 6 就有所不同。說(shuō)到 JDK 6,可能有些讀者表示不服,JDK 6?什么年代了,JDK 13 都出來(lái)了好不好?但我想告訴大家的是,對(duì)比著剖析 JDK 的源碼,對(duì)學(xué)習(xí)大有裨益。

不是有那么一句話嘛,要想了解一個(gè)成功人士,不能只關(guān)注他發(fā)跡以后的事,更要關(guān)注他之前做了什么。

就請(qǐng)隨我來(lái),看看 JDK 6 中的 substring() 的源碼吧。

 

  1. //JDK 6 
  2. String(int offset, int countchar value[]) { 
  3.     this.value = value; 
  4.     this.offset = offset; 
  5.     this.count = count
  6.   
  7. public String substring(int beginIndex, int endIndex) { 
  8.     //check boundary 
  9.     return  new String(offset + beginIndex, endIndex - beginIndex, value); 

substring() 方法本身和 JDK 7 并沒(méi)有很大的差別,都通過(guò) new String() 返回了一個(gè)新的字符串對(duì)象。但是 String() 這個(gè)構(gòu)造函數(shù)有很大的差別,JDK 6 只是簡(jiǎn)單地更改了一下兩個(gè)屬性(offset 和 count)的值,value 并沒(méi)有變。

PS:value 是真正存儲(chǔ)字符的數(shù)組,offset 是數(shù)組中第一個(gè)元素的下標(biāo),count 是數(shù)組中字符的個(gè)數(shù)。

這意味著什么呢?

調(diào)用 substring() 的時(shí)候雖然創(chuàng)建了新的字符串,但字符串的值仍然指向的是內(nèi)存中的同一個(gè)數(shù)組,如下圖所示。

 

 

 

 

3、為什么 JDK 7 的構(gòu)造函數(shù)發(fā)生了變化

看了 JDK 6 和 JDK 7 源碼之后,大家可能產(chǎn)生這樣一個(gè)疑惑:為什么 JDK 7 要做出改變呢?大家共用同一個(gè)字符串?dāng)?shù)組不是挺好的嘛,省得占用新的內(nèi)存空間。事實(shí)上呢?

如果有一個(gè)很長(zhǎng)很長(zhǎng)的字符串,可以繞地球一周,當(dāng)我們需要調(diào)用 substring() 截取其中很小一段字符串時(shí),就有可能導(dǎo)致性能問(wèn)題。由于這一小段字符串引用了整個(gè)很長(zhǎng)很長(zhǎng)的字符數(shù)組,就導(dǎo)致很長(zhǎng)很長(zhǎng)的這個(gè)字符數(shù)組無(wú)法被回收,內(nèi)存一直被占用著,就有可能引發(fā)內(nèi)存泄露。

PS:內(nèi)存泄露是指由于疏忽或錯(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存。

那 JDK 7 出現(xiàn)之前,這個(gè)隱患怎么應(yīng)對(duì)呢?答案如下。

 

  1. cmower = cmower.substring(0, 4) + ""

為什么,為什么,為什么,多一個(gè) “+ ""” 就能解決內(nèi)存泄漏的問(wèn)題?有些讀者可能不太相信,我來(lái)帶大家分析一下。

首先呢,我們通過(guò) JAD 對(duì)字節(jié)碼反編譯一下,上面這行代碼就變成了如下內(nèi)容。

 

  1. cmower = (new StringBuilder(String.valueOf(cmower.substring(0, 4)))).toString(); 

“+”號(hào)操作符就相當(dāng)于一個(gè)語(yǔ)法糖,加上空的字符串后,會(huì)被 JDK 轉(zhuǎn)化為 StringBuilder 對(duì)象,該對(duì)象在處理字符串的時(shí)候會(huì)生成新的字符數(shù)組,所以 cmower = cmower.substring(0, 4) + ""; 這行代碼執(zhí)行后,cmower 就指向了和 substring() 調(diào)用之前不同的字符數(shù)組。

PS:如果不明白“+”號(hào)操作符的工作原理,請(qǐng)查閱我之前寫(xiě)的文章《羞,Java 字符串拼接竟然有這么多姿勢(shì)》,這里就不再贅述,免得被老讀者捶。

4、最后

總結(jié)一下,JDK 7 和 JDK 6 的 substring() 方法本身并沒(méi)有多大的改變,但 String 類的構(gòu)造函數(shù)有了很大的區(qū)別,JDK 7 會(huì)重新復(fù)制一份字符數(shù)組,而 JDK 6 不會(huì),因此 JDK 6 在執(zhí)行比較長(zhǎng)的字符串 substring() 時(shí)可能會(huì)引發(fā)內(nèi)存泄露的問(wèn)題。

責(zé)任編輯:華軒 來(lái)源: Java極客技術(shù)
相關(guān)推薦

2020-06-02 07:44:04

AQS JavaNode

2019-07-29 10:10:06

Java內(nèi)存線程安全

2020-05-29 11:48:01

安全運(yùn)維信息安全網(wǎng)絡(luò)安全

2019-08-12 11:14:00

JVM垃圾對(duì)象

2019-08-01 10:20:10

2020-05-22 08:13:45

敏捷開(kāi)發(fā)OKR

2022-12-12 08:46:11

2022-05-30 18:37:03

數(shù)據(jù)個(gè)人信息人工智能

2022-08-26 01:10:32

TCPSYNLinux

2022-03-16 18:27:39

開(kāi)發(fā)低代碼軟件開(kāi)發(fā)

2025-04-07 00:00:00

云原生架構(gòu)Kubernetes

2017-11-17 09:13:31

Java注解

2021-06-02 09:47:48

RSA2021

2023-06-16 14:10:00

TCPUDP網(wǎng)絡(luò)通信

2021-05-26 05:22:48

SQL 數(shù)據(jù)庫(kù)SELECT

2012-06-20 10:01:55

開(kāi)源云計(jì)算

2021-05-10 17:20:55

AIOps開(kāi)發(fā)人員人工智能

2011-08-08 13:45:58

jQuery

2021-03-12 09:24:58

Redis面試場(chǎng)景

2023-03-06 00:27:02

Kubernetesscheduler系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)