前言
因為沒有成功地為IDEA配上反編譯工具许赃,所以自己下載了一個XJad工具子眶,背景是白色的,所以忍著強迫癥硬是把IDEA的主體也給換成白色了歼冰,感覺為了這篇文章付出了諸多啊....
字符串簡介
《Thinging in Java》中有一句話:可以證明美尸,字符串操作是計算機程序設(shè)計中最常見的行為冤议。
把多個字符按照一定的順序排列起來,就叫字符串(就像羊肉串一樣师坎,串起來的)恕酸,具體是怎么排列的,你可以跟進String的源代碼去看一下胯陋,會發(fā)現(xiàn)它其實內(nèi)部維護的是一個char類型的數(shù)組:
// 也就是說
String str = "ABCD"; // 定義一個字符串對象蕊温,其實等價于:
char[] cr = new char[]{'A','B','C','D'};
字符串的分類
其實說起來會有些別扭,為什么字符串會有分類這種東西遏乔。了解的朋友可能會知道字符串的操作除了String义矛,還有StringBuffer和StringBuilder(區(qū)別我們在下面來說)
不可變的字符串
String是一個奇葩。
String對象不可變盟萨,也就是說當(dāng)對象創(chuàng)建完畢之后凉翻,該對象的內(nèi)容(字符序列)是不允許改變的,如果內(nèi)容改變則會創(chuàng)建一個新的String對象捻激,返回到原地址中制轰。
細(xì)心的朋友也許會發(fā)現(xiàn)前计,String類維護的char數(shù)組不僅被final所修飾,并且查看JDK源碼你就會發(fā)現(xiàn)垃杖,String類中每一個看起來會修改String值得方法男杈,實際上都是創(chuàng)建了一個全新的String對象,以包含修改后的字符串對象调俘。而最初的String對象則絲毫未動伶棒。我們可以簡單的來看一個實例(從替換操作中就能明顯看出):
replace方法就是替換字符串中的內(nèi)容,如果替換之后跟原來的字符串相同則返回this彩库,如果不相同則new一個新的對象返回肤无。這明顯體現(xiàn)了內(nèi)容改變則返回新對象而不是直接修改String對象的值。
表面的錯覺
關(guān)于String對象是否可變骇钦,有些操作確實會給人錯覺舅锄,先來看一段程序:
從結(jié)果來看,s1的值最初是“A”司忱,經(jīng)過賦值以后,變成了“C”,經(jīng)過字符串連接運算并賦值以后畴蹭,變成了“BC”坦仍。String對象的內(nèi)容真的改變了嗎?實際上叨襟,這只是錯覺而已繁扎。有疑惑的朋友可以去看我的上一篇筆記,你就能知道:
String對象“A”糊闽,“B”梳玫,“C”在全程中都沒有任何改變,改變的只是引用s1所指向的內(nèi)容右犹,也就是s1的值提澎。
String對象的創(chuàng)建
有兩種方式:
// 第一種:直接賦一個字面量
String str1 = "ABCD";
// 第二種:通過構(gòu)造器創(chuàng)建
String str2 = new String("ABCD");
那么這兩種方式有什么不同呢?這里可能會涉及到一個面試題:
上述的兩種方法分別創(chuàng)建了幾個String對象念链?
回答這個問題也特別簡單盼忌,首先你需要直到JVM的內(nèi)存模型是怎樣的,在上一篇筆記中也有簡單提到掂墓,這里需要補充的是:常量池(專門存儲常量的地方谦纱,都指的是方法區(qū)中)分為編譯常量池(不研究,存儲字節(jié)碼的相關(guān)信息)和運行常量池(存儲常量數(shù)據(jù))君编。
先來看一張結(jié)果圖:
結(jié)果圖
- 當(dāng)執(zhí)行第一句話的時候跨嘉,會在常量池中添加一個新的ABCD字符,str1指向常量池的ABCD
- 當(dāng)執(zhí)行第二句話的時候吃嘿,因為有new操作符祠乃,所以會在堆空間新開辟一塊空間用來存儲新的String對象梦重,因為此時常量池中已經(jīng)有了ABCD字符,所以堆中的String對象指向常量池中的ABCD跳纳,而str2則指向堆空間中的String對象忍饰。
所以結(jié)論:
String str1 = "ABCD";
最多創(chuàng)建一個String對象,最少不創(chuàng)建String對象.如果常量池中,存在”ABCD”,那么str1直接引用,此時不創(chuàng)建String對象.否則,先在常量池先創(chuàng)建”ABCD”內(nèi)存空間,再引用.
String str2 = new String("ABCD");
最多創(chuàng)建兩個String對象,至少創(chuàng)建一個String對象寺庄。new關(guān)鍵字絕對會在堆空間創(chuàng)建一塊新的內(nèi)存區(qū)域艾蓝,所以至少創(chuàng)建一個String對象。
String對象的空值
一種是表示引用為空(null)的空值:
String str1 = null; // 沒有初始化斗塘,沒有分配內(nèi)存空間
另外一種表示內(nèi)容為空的空值:
String str2 = "; // 分配有內(nèi)存空間赢织,有內(nèi)容。
所以當(dāng)你需要判斷字符串是否為空的時候馍盟,實際上應(yīng)該這樣:
判斷字符串非空
字符串的比較
從上圖可以明顯看出于置,使用“==”,只能比較引用的內(nèi)存地址是否相同贞岭,而使用“equals”方法八毯,則比較的是字符串的內(nèi)容。
我們可以跟到String類的equals方法:
String類的equals方法
“+”號是怎么來連接字符串的
先來直接看一個簡單的例子瞄桨,程序中創(chuàng)建了三個String對象话速,str是hello和wrold兩個字符串連接賦值后的對象,程序的結(jié)果很明顯芯侥,但我們關(guān)心的是泊交,hello和world是怎樣連接起來的呢?
我們在XJad(Java反編譯程序柱查,把生成的class反編譯成java)中打開剛剛生成的class文件會發(fā)現(xiàn):
編譯器自動引入了一個java.lang.StringBuilder類廓俭。雖然我們在源代碼中并沒有使用StringBuilder類,但是編譯器卻自作主張地使用了它唉工,因為它更高效研乒。
在這個例子中,編譯器創(chuàng)建了一個StringBuilde對象酵紫,用以構(gòu)造最終的String告嘲,并為每個字符串調(diào)用了一次StringBuilder的append()方法,總計兩次奖地。最后調(diào)用toString()生成結(jié)果橄唬。這是編譯器自動優(yōu)化的結(jié)果,包括自動生成的Tester()無參數(shù)默認(rèn)的構(gòu)造函數(shù)也是参歹。
現(xiàn)在仰楚,你也許會覺得可以隨意使用String對象,反正編譯器會為你自動地優(yōu)化性能∩纾可是在這之前侨嘀,我們先要看看編譯器究竟能給我們優(yōu)化到什么程度(下面再詳細(xì)介紹StringBuilder)。
可變的字符串
StringBuilder/StringBuffer:當(dāng)對象創(chuàng)建完畢之后捂襟,該對象的內(nèi)容可以發(fā)生改變咬腕,當(dāng)內(nèi)容發(fā)生改變的時候,對象保持不變葬荷。
接著上面的問題涨共,我們繼續(xù)來看一個例子:
程序和程序的結(jié)果
程序的結(jié)果顯而易見,我們來看看反編譯之后的代碼:
反編譯之后的代碼
可以看到宠漩,對比兩個對象举反,后者的循環(huán)部分的代碼更簡短、更簡單扒吁,而且它只生成了一個StringBuilder對象火鼻。
結(jié)論是:如果字符串操作比較簡單,那就可以信賴編譯器雕崩,它會為你合理地構(gòu)造最終的字符串結(jié)果魁索。但如果你還使用循環(huán),多次地改變字符串的內(nèi)容盼铁,那就更適合StringBuilder對象蛾默。
但是如果你想要走捷徑,例如append(a+":"+c)捉貌,則編譯器就會調(diào)入陷阱,從而為你另外創(chuàng)建一個StringBuilder對象處理括號內(nèi)的字符串操作冬念。
String對象的比較
StringBuilder是Java SE5引入的趁窃,在這之前Java用的是StringBuffer。后者線程安全(只需要了解急前,該對象方法中所有的方法都是用了synchronized修飾符)醒陆,因此開銷也會大。有沒有用synchronized修飾符裆针,就是這兩者唯一的區(qū)別刨摩。我們可以簡單地來比較一下這三個String對象在拼接字符串中的性能:
創(chuàng)建好三個方法,分別測試三個類型的對象的拼接效率:
測試拼接效率
最后在main方法中測試
面試題
最后再有一個String的面試題:
說說下面的String對象世吨,彼此之間是否相等澡刹?
面試題
如果你自己寫幾個判斷相等的語句,分別判斷str1和另外五個是否相等耘婚,則會發(fā)現(xiàn):
str1和str2/str3相等罢浇,和另外幾個都不相等。我們先來看一下反編譯之后的代碼:
編譯之后的代碼(存在編譯優(yōu)化)
知識點(純干貨):
- 單獨使用""引號創(chuàng)建的字符串都是直接量,編譯期就已經(jīng)確定存儲到常量池中;
- 使用new String("")創(chuàng)建的對象會存儲到堆內(nèi)存中,是運行期才創(chuàng)建嚷闭;
- 使用只包含直接量的字符串連接符如"aa" + "bb"創(chuàng)建的也是直接量編譯期就能確定,已經(jīng)確定存儲到常量池中(str2和str3)攒岛;
- 使用包含String直接量(無final修飾符)的字符串表達(dá)式(如"aa" + s1)創(chuàng)建的對象是運行期才創(chuàng)建的,存儲在堆中;
- 通過變量/調(diào)用方法去連接字符串,都只能在運行時期才能確定變量的值和方法的返回值,不存在編譯優(yōu)化操作.
文章結(jié)尾
其實還想寫關(guān)于正則表達(dá)的東西的胞锰,還是改天找時間另外研究研究寫一篇像樣的吧灾锯。關(guān)于String的操作,就簡單給一下圖吧嗅榕,感興趣也可以自己百度或者跟蹤進源代碼里面去看顺饮,這里就不細(xì)說了:
參考資料:
- http://study.163.com/course/courseMain.htm?courseId=1003108028 《Java零基礎(chǔ)入門教程》
- 《Thinking in Java》第四版
歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處誊册!
簡書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關(guān)注公眾微信號:wmyskxz
分享自己的學(xué)習(xí) & 學(xué)習(xí)資料 & 生活
想要交流的朋友也可以加qq群:3382693