在Java的學(xué)習(xí)過(guò)程中,字符串的處理是絕對(duì)繞不過(guò)去的檻详恼。初學(xué)時(shí)總是被奇怪的String搞的莫名其妙,也曾經(jīng)對(duì)面試中各種String的相等搞到暈頭轉(zhuǎn)向。
在學(xué)習(xí)過(guò)JVM的機(jī)制和閱讀過(guò)JDK源碼后才算是撥開(kāi)云霧識(shí)得廬山真面目媚创。
String類
我們經(jīng)常說(shuō)String類是不可變類,說(shuō)String對(duì)象是不可變對(duì)象彤恶,具體是因?yàn)槭裁茨兀?/p>
從String的源碼來(lái)看钞钙,String類被final關(guān)鍵字修飾使得它成為不可變類。不可變類的特性使得我們不能繼承String類來(lái)實(shí)現(xiàn)新的類声离,并且String類中的方法也默認(rèn)為final方法芒炼,無(wú)法被覆寫。
String類的底層是使用了字符數(shù)組char[]來(lái)進(jìn)行存儲(chǔ)术徊,從String對(duì)象的操作處理來(lái)看焕议,每次當(dāng)我們?cè)噲D去改變String對(duì)象的時(shí)候,實(shí)際上都沒(méi)有修改到原來(lái)的對(duì)象弧关,而是產(chǎn)生并返回了一個(gè)新的對(duì)象盅安,包括使用“+”運(yùn)算符進(jìn)行操作。
String的相等
你肯定不止一次的在筆試題中遇到讓你判斷字符串是否相等的題目世囊。其實(shí)基于上述String的特性再加一點(diǎn)JVM的知識(shí)别瞭,再也沒(méi)有什么能難倒你。
需要明確的包括下面幾個(gè)關(guān)鍵點(diǎn):
String對(duì)象為不可變對(duì)象株憾,對(duì)它的修改會(huì)生成一個(gè)新的對(duì)象(新的內(nèi)存區(qū)域)并返回蝙寨。
在編譯期的字面量和符號(hào)引用會(huì)被直接編譯存儲(chǔ)在class類的常量池,如:String s = “hello”嗤瞎,在運(yùn)行期隨著類的加載進(jìn)入運(yùn)行期常量池墙歪。
通過(guò)new關(guān)鍵字創(chuàng)建的對(duì)象會(huì)在堆內(nèi)存進(jìn)行分配。
字面量和字面量的“+”操作在編譯期即被優(yōu)化為最終的結(jié)果贝奇。String s = “hello” + “world”即等同于String s = “hello world”虹菲。但引用值和字面量的操作不會(huì)被優(yōu)化。
final關(guān)鍵字修飾的變量會(huì)被編譯存儲(chǔ)到常量池掉瞳,在進(jìn)行“+”操作時(shí)等同于字面量毕源,會(huì)直接被優(yōu)化浪漠。
String類的intern()方法會(huì)在常量池創(chuàng)建指定的值,如果已經(jīng)存在則直接返回霎褐。
舉例1
String a = “hello world”址愿,String b = “hello” + “world”; System.out.println(a == b);
結(jié)果:true
b的定義為字面量直接相加,因此會(huì)在編譯時(shí)進(jìn)行優(yōu)化冻璃,查看反編譯的類可以看到類中b的定義為String b = “hello world”响谓。
舉例2
String a = “hello world”, String b = a + “world”; final String c = “hello”, String d = c + “world”; System.out.println(a == b); System.out.println( a == d);
結(jié)果:false;true
因b的定義是通過(guò)引用和字面量相加得到省艳,所以并不會(huì)被優(yōu)化娘纷。從反編譯類可以可以看出b的定義并沒(méi)有發(fā)生變化,因此b會(huì)在運(yùn)行期創(chuàng)建和分配拍埠。而c因?yàn)橛衒inal關(guān)鍵字修飾失驶,從而d在編譯期會(huì)直接被優(yōu)化為“hello world”
舉例3
String a = “hello world”, String b = new String(“hello world”), String c = b.intern(); System.out.println(a == b); System.out.println(a == c)
結(jié)果:false, true
因a為字面常量,而b使用new來(lái)創(chuàng)建枣购,會(huì)在堆內(nèi)存進(jìn)行分配嬉探,因此a==b結(jié)果為false;
C通過(guò)String的intern()方法在常量池創(chuàng)建,而”hello world”已經(jīng)存在棉圈,所以直接返回與a相同的引用涩堤。
StringBuilder和StringBuffer
首先看下StringBuilder和StringBuffer的區(qū)別。從源碼可以很清楚看出兩個(gè)類都是繼承自同一個(gè)類分瘾,所以底層的實(shí)現(xiàn)基本相同胎围,唯一的區(qū)別在于StringBuffer是設(shè)計(jì)為線程安全的,所以提供的公共方法都增加synchroinzed關(guān)鍵字來(lái)保證同步德召。也因此在使用上StringBuffer的效率會(huì)比StringBuilder要低白魂。所以在不需要考慮線程安全的情況下,我們通常選擇StringBuilder上岗。
其次福荸,設(shè)計(jì)StringBuilder或者StringBuffer的意義何在?
從上面我們知道String對(duì)象的不可變性導(dǎo)致當(dāng)我們對(duì)String對(duì)象進(jìn)行修改時(shí)總是會(huì)創(chuàng)建一個(gè)新對(duì)象(涉及背后的一系列內(nèi)存分配操作)肴掷,因此當(dāng)需要頻繁改變String對(duì)象時(shí)敬锐,比如常見(jiàn)的循環(huán)操作對(duì)String對(duì)象進(jìn)行修改,會(huì)造成大量的內(nèi)存分配操作導(dǎo)致效率降低呆瞻。而StringBuilder或StringBuffer實(shí)現(xiàn)了對(duì)底層存儲(chǔ)數(shù)組的直接修改來(lái)提升效率台夺。
以上就是Java字符串的那些事兒,相信以后它不會(huì)再造成困擾了痴脾。