Java學(xué)習(xí)筆記(3)—— String類詳解

前言

因?yàn)闆](méi)有成功地為IDEA配上反編譯工具,所以自己下載了一個(gè)XJad工具,背景是白色的,所以忍著強(qiáng)迫癥硬是把IDEA的主體也給換成白色了,感覺(jué)為了這篇文章付出了諸多啊....

字符串簡(jiǎn)介

《Thinging in Java》中有一句話:可以證明,字符串操作是計(jì)算機(jī)程序設(shè)計(jì)中最常見(jiàn)的行為。

把多個(gè)字符按照一定的順序排列起來(lái),就叫字符串(就像羊肉串一樣,串起來(lái)的),具體是怎么排列的,你可以跟進(jìn)String的源代碼去看一下,會(huì)發(fā)現(xiàn)它其實(shí)內(nèi)部維護(hù)的是一個(gè)char類型的數(shù)組:

String類內(nèi)部維護(hù)的是一個(gè)char數(shù)組
// 也就是說(shuō)
String str = "ABCD";      // 定義一個(gè)字符串對(duì)象,其實(shí)等價(jià)于:
char[] cr = new char[]{'A','B','C','D'};

字符串的分類

其實(shí)說(shuō)起來(lái)會(huì)有些別扭,為什么字符串會(huì)有分類這種東西。了解的朋友可能會(huì)知道字符串的操作除了String,還有StringBuffer和StringBuilder(區(qū)別我們?cè)谙旅鎭?lái)說(shuō))

不可變的字符串

String是一個(gè)奇葩。

String對(duì)象不可變,也就是說(shuō)當(dāng)對(duì)象創(chuàng)建完畢之后,該對(duì)象的內(nèi)容(字符序列)是不允許改變的,如果內(nèi)容改變則會(huì)創(chuàng)建一個(gè)新的String對(duì)象,返回到原地址中。

細(xì)心的朋友也許會(huì)發(fā)現(xiàn),String類維護(hù)的char數(shù)組不僅被final所修飾,并且查看JDK源碼你就會(huì)發(fā)現(xiàn),String類中每一個(gè)看起來(lái)會(huì)修改String值得方法,實(shí)際上都是創(chuàng)建了一個(gè)全新的String對(duì)象,以包含修改后的字符串對(duì)象。而最初的String對(duì)象則絲毫未動(dòng)。我們可以簡(jiǎn)單的來(lái)看一個(gè)實(shí)例(從替換操作中就能明顯看出):

String類中的replace方法

replace方法就是替換字符串中的內(nèi)容,如果替換之后跟原來(lái)的字符串相同則返回this,如果不相同則new一個(gè)新的對(duì)象返回。這明顯體現(xiàn)了內(nèi)容改變則返回新對(duì)象而不是直接修改String對(duì)象的值。

表面的錯(cuò)覺(jué)

關(guān)于String對(duì)象是否可變,有些操作確實(shí)會(huì)給人錯(cuò)覺(jué),先來(lái)看一段程序:

一個(gè)例子

從結(jié)果來(lái)看,s1的值最初是“A”,經(jīng)過(guò)賦值以后,變成了“C”,經(jīng)過(guò)字符串連接運(yùn)算并賦值以后,變成了“BC”。String對(duì)象的內(nèi)容真的改變了嗎?實(shí)際上,這只是錯(cuò)覺(jué)而已。有疑惑的朋友可以去看我的上一篇筆記,你就能知道:

String對(duì)象“A”,“B”,“C”在全程中都沒(méi)有任何改變,改變的只是引用s1所指向的內(nèi)容,也就是s1的值。

String對(duì)象的創(chuàng)建

有兩種方式:

// 第一種:直接賦一個(gè)字面量
String str1 = "ABCD";
// 第二種:通過(guò)構(gòu)造器創(chuàng)建
String str2 = new String("ABCD");

那么這兩種方式有什么不同呢?這里可能會(huì)涉及到一個(gè)面試題:

上述的兩種方法分別創(chuàng)建了幾個(gè)String對(duì)象?

回答這個(gè)問(wèn)題也特別簡(jiǎn)單,首先你需要直到JVM的內(nèi)存模型是怎樣的,在上一篇筆記中也有簡(jiǎn)單提到,這里需要補(bǔ)充的是:常量池(專門(mén)存儲(chǔ)常量的地方,都指的是方法區(qū)中)分為編譯常量池(不研究,存儲(chǔ)字節(jié)碼的相關(guān)信息)和運(yùn)行常量池(存儲(chǔ)常量數(shù)據(jù))。

先來(lái)看一張結(jié)果圖:


結(jié)果圖
  • 當(dāng)執(zhí)行第一句話的時(shí)候,會(huì)在常量池中添加一個(gè)新的ABCD字符,str1指向常量池的ABCD
  • 當(dāng)執(zhí)行第二句話的時(shí)候,因?yàn)橛衝ew操作符,所以會(huì)在堆空間新開(kāi)辟一塊空間用來(lái)存儲(chǔ)新的String對(duì)象,因?yàn)榇藭r(shí)常量池中已經(jīng)有了ABCD字符,所以堆中的String對(duì)象指向常量池中的ABCD,而str2則指向堆空間中的String對(duì)象。

所以結(jié)論:
String str1 = "ABCD";
最多創(chuàng)建一個(gè)String對(duì)象,最少不創(chuàng)建String對(duì)象.如果常量池中,存在”ABCD”,那么str1直接引用,此時(shí)不創(chuàng)建String對(duì)象.否則,先在常量池先創(chuàng)建”ABCD”內(nèi)存空間,再引用.
String str2 = new String("ABCD");
最多創(chuàng)建兩個(gè)String對(duì)象,至少創(chuàng)建一個(gè)String對(duì)象。new關(guān)鍵字絕對(duì)會(huì)在堆空間創(chuàng)建一塊新的內(nèi)存區(qū)域,所以至少創(chuàng)建一個(gè)String對(duì)象。

String對(duì)象的空值

一種是表示引用為空(null)的空值:

String str1 = null;  // 沒(méi)有初始化,沒(méi)有分配內(nèi)存空間

另外一種表示內(nèi)容為空的空值:

String str2 = ";  // 分配有內(nèi)存空間,有內(nèi)容。

所以當(dāng)你需要判斷字符串是否為空的時(shí)候,實(shí)際上應(yīng)該這樣:


判斷字符串非空

字符串的比較

字符串的比較

從上圖可以明顯看出,使用“==”,只能比較引用的內(nèi)存地址是否相同,而使用“equals”方法,則比較的是字符串的內(nèi)容。

我們可以跟到String類的equals方法:


String類的equals方法

“+”號(hào)是怎么來(lái)連接字符串的

先來(lái)直接看一個(gè)簡(jiǎn)單的例子,程序中創(chuàng)建了三個(gè)String對(duì)象,str是hello和wrold兩個(gè)字符串連接賦值后的對(duì)象,程序的結(jié)果很明顯,但我們關(guān)心的是,hello和world是怎樣連接起來(lái)的呢?

先來(lái)看一個(gè)例子

我們?cè)赬Jad(Java反編譯程序,把生成的class反編譯成java)中打開(kāi)剛剛生成的class文件會(huì)發(fā)現(xiàn):

反編譯的結(jié)果

編譯器自動(dòng)引入了一個(gè)java.lang.StringBuilder類。雖然我們?cè)谠创a中并沒(méi)有使用StringBuilder類,但是編譯器卻自作主張地使用了它,因?yàn)樗咝А?/p>

在這個(gè)例子中,編譯器創(chuàng)建了一個(gè)StringBuilde對(duì)象,用以構(gòu)造最終的String,并為每個(gè)字符串調(diào)用了一次StringBuilderappend()方法,總計(jì)兩次。最后調(diào)用toString()生成結(jié)果。這是編譯器自動(dòng)優(yōu)化的結(jié)果,包括自動(dòng)生成的Tester()無(wú)參數(shù)默認(rèn)的構(gòu)造函數(shù)也是。

現(xiàn)在,你也許會(huì)覺(jué)得可以隨意使用String對(duì)象,反正編譯器會(huì)為你自動(dòng)地優(yōu)化性能。可是在這之前,我們先要看看編譯器究竟能給我們優(yōu)化到什么程度(下面再詳細(xì)介紹StringBuilder)。

可變的字符串

StringBuilder/StringBuffer:當(dāng)對(duì)象創(chuàng)建完畢之后,該對(duì)象的內(nèi)容可以發(fā)生改變,當(dāng)內(nèi)容發(fā)生改變的時(shí)候,對(duì)象保持不變。

接著上面的問(wèn)題,我們繼續(xù)來(lái)看一個(gè)例子:

程序和程序的結(jié)果

程序的結(jié)果顯而易見(jiàn),我們來(lái)看看反編譯之后的代碼:
反編譯之后的代碼

可以看到,對(duì)比兩個(gè)對(duì)象,后者的循環(huán)部分的代碼更簡(jiǎn)短、更簡(jiǎn)單,而且它只生成了一個(gè)StringBuilder對(duì)象。

結(jié)論是:如果字符串操作比較簡(jiǎn)單,那就可以信賴編譯器,它會(huì)為你合理地構(gòu)造最終的字符串結(jié)果。但如果你還使用循環(huán),多次地改變字符串的內(nèi)容,那就更適合StringBuilder對(duì)象。

但是如果你想要走捷徑,例如append(a+":"+c),則編譯器就會(huì)調(diào)入陷阱,從而為你另外創(chuàng)建一個(gè)StringBuilder對(duì)象處理括號(hào)內(nèi)的字符串操作。

編譯器陷阱

String對(duì)象的比較

StringBuilder是Java SE5引入的,在這之前Java用的是StringBuffer。后者線程安全(只需要了解,該對(duì)象方法中所有的方法都是用了synchronized修飾符),因此開(kāi)銷也會(huì)大。有沒(méi)有用synchronized修飾符,就是這兩者唯一的區(qū)別。我們可以簡(jiǎn)單地來(lái)比較一下這三個(gè)String對(duì)象在拼接字符串中的性能:

創(chuàng)建好三個(gè)方法,分別測(cè)試三個(gè)類型的對(duì)象的拼接效率:


測(cè)試拼接效率

最后在main方法中測(cè)試

面試題

最后再有一個(gè)String的面試題:

說(shuō)說(shuō)下面的String對(duì)象,彼此之間是否相等?

面試題

如果你自己寫(xiě)幾個(gè)判斷相等的語(yǔ)句,分別判斷str1和另外五個(gè)是否相等,則會(huì)發(fā)現(xiàn):
str1和str2/str3相等,和另外幾個(gè)都不相等。我們先來(lái)看一下反編譯之后的代碼:

編譯之后的代碼(存在編譯優(yōu)化)

知識(shí)點(diǎn)(純干貨):

  • 單獨(dú)使用""引號(hào)創(chuàng)建的字符串都是直接量,編譯期就已經(jīng)確定存儲(chǔ)到常量池中;
  • 使用new String("")創(chuàng)建的對(duì)象會(huì)存儲(chǔ)到堆內(nèi)存中,是運(yùn)行期才創(chuàng)建;
  • 使用只包含直接量的字符串連接符如"aa" + "bb"創(chuàng)建的也是直接量編譯期就能確定,已經(jīng)確定存儲(chǔ)到常量池中(str2和str3);
  • 使用包含String直接量(無(wú)final修飾符)的字符串表達(dá)式(如"aa" + s1)創(chuàng)建的對(duì)象是運(yùn)行期才創(chuàng)建的,存儲(chǔ)在堆中;
  • 通過(guò)變量/調(diào)用方法去連接字符串,都只能在運(yùn)行時(shí)期才能確定變量的值和方法的返回值,不存在編譯優(yōu)化操作.

文章結(jié)尾

其實(shí)還想寫(xiě)關(guān)于正則表達(dá)的東西的,還是改天找時(shí)間另外研究研究寫(xiě)一篇像樣的吧。關(guān)于String的操作,就簡(jiǎn)單給一下圖吧,感興趣也可以自己百度或者跟蹤進(jìn)源代碼里面去看,這里就不細(xì)說(shuō)了:

String類中常用的方法

參考資料:


歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明出處!
簡(jiǎn)書(shū)ID:@我沒(méi)有三顆心臟
github:wmyskxz
歡迎關(guān)注公眾微信號(hào):wmyskxz
分享自己的學(xué)習(xí) & 學(xué)習(xí)資料 & 生活
想要交流的朋友也可以加qq群:3382693

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容