传一个实体一个string_没想到,一个小小的String还有这么多窍门
1. 看看源碼
大家都知道, String 被聲明為 final,因此它不可被繼承。(Integer 等包裝類(lèi)也不能被繼承)。我們先來(lái)看看 String 的源碼。
在 Java 8 中,String 內(nèi)部使用 char 數(shù)組存儲(chǔ)數(shù)據(jù)。
在 Java 9 之后,String 類(lèi)的實(shí)現(xiàn)改用 byte 數(shù)組存儲(chǔ)字符串,同時(shí)使用 coder 來(lái)標(biāo)識(shí)使用了哪種編碼。
value 數(shù)組被聲明為 final,這意味著 value 數(shù)組初始化之后就不能再引用其它數(shù)組。并且 String 內(nèi)部沒(méi)有改變 value 數(shù)組的方法,因此可以保證 String 不可變。
2. 不可變有什么好處呢
2.1 可以緩存 hash 值
因?yàn)?String 的 hash 值經(jīng)常被使用,例如 String 用做 HashMap 的 key。不可變的特性可以使得 hash 值也不可變,因此只需要進(jìn)行一次計(jì)算。
2.2 String Pool 的使用
如果一個(gè) String 對(duì)象已經(jīng)被創(chuàng)建過(guò)了,那么就會(huì)從 String Pool 中取得引用。只有 String 是不可變的,才可能使用 String Pool。
2.3 安全性
String 經(jīng)常作為參數(shù),String 不可變性可以保證參數(shù)不可變。例如在作為網(wǎng)絡(luò)連接參數(shù)的情況下如果 String 是可變的,那么在網(wǎng)絡(luò)連接過(guò)程中,String 被改變,改變 String 的那一方以為現(xiàn)在連接的是其它主機(jī),而實(shí)際情況卻不一定是。
2.4 線程安全
String 不可變性天生具備線程安全,可以在多個(gè)線程中安全地使用。
3. 再來(lái)深入了解一下 String
3.1 “+” 連接符
字符串對(duì)象可以使用“+”連接其他對(duì)象,其中字符串連接是通過(guò) StringBuilder(或 StringBuffer)類(lèi)及其 append 方法實(shí)現(xiàn)的,對(duì)象轉(zhuǎn)換為字符串是通過(guò) toString 方法實(shí)現(xiàn)的。可以通過(guò)反編譯驗(yàn)證一下:
由上可以看出,Java中使用"+"連接字符串對(duì)象時(shí),會(huì)創(chuàng)建一個(gè)StringBuilder()對(duì)象,并調(diào)用append()方法將數(shù)據(jù)拼接,最后調(diào)用toString()方法返回拼接好的字符串。那這個(gè) “+” 的效率怎么樣呢?
3.2 “+”連接符的效率
使用“+”連接符時(shí),JVM會(huì)隱式創(chuàng)建StringBuilder對(duì)象,這種方式在大部分情況下并不會(huì)造成效率的損失,不過(guò)在進(jìn)行大量循環(huán)拼接字符串時(shí)則需要注意。比如:
這樣由于大量StringBuilder創(chuàng)建在堆內(nèi)存中,肯定會(huì)造成效率的損失,所以在這種情況下建議在循環(huán)體外創(chuàng)建一個(gè)StringBuilder對(duì)象調(diào)用append()方法手動(dòng)拼接(如上面例子如果使用手動(dòng)拼接運(yùn)行時(shí)間將縮小到1/200左右)。
與此之外還有一種特殊情況,也就是當(dāng)"+"兩端均為編譯期確定的字符串常量時(shí),編譯器會(huì)進(jìn)行相應(yīng)的優(yōu)化,直接將兩個(gè)字符串常量拼接好,例如:
4. 字符串常量
4.1 為什么使用字符串常量?
JVM為了提高性能和減少內(nèi)存的開(kāi)銷(xiāo),在實(shí)例化字符串的時(shí)候進(jìn)行了一些優(yōu)化:使用字符串常量池。每當(dāng)創(chuàng)建字符串常量時(shí),JVM會(huì)首先檢查字符串常量池,如果該字符串已經(jīng)存在常量池中,那么就直接返回常量池中的實(shí)例引用。如果字符串不存在常量池中,就會(huì)實(shí)例化該字符串并且將其放到常量池中。由于String字符串的不可變性,常量池中一定不存在兩個(gè)相同的字符串。
4.2 實(shí)現(xiàn)字符串常量池的基礎(chǔ)
實(shí)現(xiàn)該優(yōu)化的基礎(chǔ)是因?yàn)樽址遣豢勺兊?#xff0c;可以不用擔(dān)心數(shù)據(jù)沖突進(jìn)行共享。
運(yùn)行時(shí)實(shí)例創(chuàng)建的全局字符串常量池中有一個(gè)表,總是為池中每個(gè)唯一的字符串對(duì)象維護(hù)一個(gè)引用,這就意味著它們一直引用著字符串常量池中的對(duì)象,所以,在常量池中的這些字符串不會(huì)被垃圾收集器回收。
我們來(lái)看個(gè)小例子,了解下不同的方式創(chuàng)建的字符串在內(nèi)存中的位置:
5. String類(lèi)常見(jiàn)的面試題
5.1 判斷字符串s1和s2是否相等
解析:
s1和s2:
String s1 = "123";先是在字符串常量池創(chuàng)建了一個(gè)字符串常量“123”,“123”常量是有地址值,地址值賦值給s1。接著聲明 String s2=“123”,由于s1已經(jīng)在方法區(qū)的常量池創(chuàng)建字符串常量"123",進(jìn)入常量池規(guī)則:如果常量池中沒(méi)有這個(gè)常量,就創(chuàng)建一個(gè),如果有就不再創(chuàng)建了,故直接把常量"123"的地址值賦值給s2,所以s1==s2為true。
由于String類(lèi)重寫(xiě)了equals方法,s1.equals(s2)比較的是字符串的內(nèi)容,s1和s2的內(nèi)容都是"123",故s1.equals(s2)為true。
s3和s4:
s3創(chuàng)建了一個(gè)新的字符串"1234",s4是兩個(gè)新的字符串"12"和"34"通過(guò)"+“符號(hào)連接所得,根據(jù)Java中常量?jī)?yōu)化機(jī)制, “12” 和"34"兩個(gè)字符串常量在編譯期就連接創(chuàng)建了字符串"1234”,由于字符串"1234"在常量池中存在,故直接把"1234"在常量池的地址賦值給s4,所以s3==s4為true。
s3和s5:
s5是由一個(gè)變量s1連接一個(gè)新的字符串"4",首先會(huì)在常量池創(chuàng)建字符串"4",然后進(jìn)行"+“操作,根據(jù)字符串的串聯(lián)規(guī)則,s5會(huì)在堆內(nèi)存中創(chuàng)建StringBuilder(或StringBuffer)對(duì)象,通過(guò)append方法拼接s1和字符串常量"4”,此時(shí)拼接成的字符串"1234"是StringBuilder(或StringBuffer)類(lèi)型的對(duì)象,通過(guò)調(diào)用toString方法轉(zhuǎn)成String對(duì)象"1234",所以s5此時(shí)實(shí)際指向的是堆內(nèi)存中的"1234"對(duì)象,堆內(nèi)存中對(duì)象的地址和常量池中對(duì)象的地址不一致,故s3==s5為false。
看下JDK8的API文檔里的解釋:
Java語(yǔ)言為字符串連接運(yùn)算符(+)提供特殊支持,并為其他對(duì)象轉(zhuǎn)換為字符串。字符串連接是通過(guò)StringBuilder (或StringBuffer )類(lèi)及其append方法實(shí)現(xiàn)的。字符串轉(zhuǎn)換是通過(guò)方法來(lái)實(shí)現(xiàn)toString,由下式定義0bject和繼承由在Java中的所有類(lèi)。有關(guān)字符串連接和轉(zhuǎn)換的其他信息,請(qǐng)參閱Gosling,Joy 和Steele,Java 語(yǔ)言規(guī)范。不管是常量池還是堆,只要是使用equals比較字符串,都是比較字符串的內(nèi)容,所以s3.equals(s5)為true。
Java常量?jī)?yōu)化機(jī)制:給一個(gè)變量賦值,如果等于號(hào)的右邊是常量,并且沒(méi)有一個(gè)變量,那么就會(huì)在編譯階段計(jì)算該表達(dá)式的結(jié)果,然后判斷該表達(dá)式的結(jié)果是否在左邊類(lèi)型所表示范圍內(nèi),如果在,那么就賦值成功,如果不在,那么就賦值失敗。但是注意如果一旦有變量參與表達(dá)式,那么就不會(huì)有編譯期間的常量?jī)?yōu)化機(jī)制。s3和s6:
String s6 = new String("1234");在堆內(nèi)存創(chuàng)建一個(gè)字符串對(duì)象,s6指向這個(gè)堆內(nèi)存的對(duì)象地址,而s3指向的是字符串常量池的"1234"對(duì)象的地址,故s3==s6為false。
5.2 創(chuàng)建多少個(gè)字符串對(duì)象?
解析:
String s0 = “123”;
字符串常量池對(duì)象:“123”,1個(gè);
共1個(gè)。
String s1 = new String(“123”);
字符串常量池對(duì)象:“123”,1個(gè);
堆對(duì)象:new String(“123”),1個(gè);
共2個(gè)。
String s2 = new String(“1” + “2”);
字符串常量池對(duì)象:“12”,1個(gè)(Jvm在編譯期做了優(yōu)化,“1” + "2"合并成了 “12”);
堆對(duì)象:new String(“12”),1個(gè)
共2個(gè)。
由于s2涉及字符串合并,我們通過(guò)命令看下字節(jié)碼信息:
得到字節(jié)碼信息如下:
我們可以很清晰看到,創(chuàng)建了一個(gè)新的String對(duì)象和一個(gè)字符串常量"12",new String("1" + "2") 相當(dāng)于 new String("12"),共創(chuàng)建了2個(gè)字符串對(duì)象。
String s3 = new String(“12”) + “3”;
字符串常量池對(duì)象:“12”、“3”,2個(gè),
堆對(duì)象: new Stringbuilder().append(“12”).append(“3”).toString();轉(zhuǎn)成String對(duì)象,1個(gè);
共3個(gè)。
我們同樣看下編譯后的結(jié)果:
可以看到,包括StringBuilder在內(nèi),共創(chuàng)建了4個(gè)對(duì)象,字符串"12"和字符串"3"是分開(kāi)創(chuàng)建的,所以共創(chuàng)建了3個(gè)字符串對(duì)象。
總結(jié):
new String()是在堆內(nèi)存創(chuàng)建新的字符串對(duì)象,其構(gòu)造參數(shù)中可傳入字符串,此字符串一般會(huì)在常量池中先創(chuàng)建出來(lái),new String()創(chuàng)建的字符串是參數(shù)字符串的副本,看下API中關(guān)于String構(gòu)造器的解釋:
String(String original)
初始化新創(chuàng)建的String對(duì)象,使其表示與參數(shù)相同的字符序列;換句話說(shuō),新創(chuàng)建的字符串是參數(shù)字符串的副本。
所以new String()的方式創(chuàng)建字符串百分百會(huì)產(chǎn)生一個(gè)新的字符串對(duì)象,而類(lèi)似于"123"這樣的字符串對(duì)象則需要在創(chuàng)建之前看常量池中有沒(méi)有,有的話就不創(chuàng)建,沒(méi)有則創(chuàng)建新的對(duì)象。 "+"操作符連接字符串常量的時(shí)候會(huì)在編譯期直接生成連接后的字符串,若該字符串在常量池已經(jīng)存在,則不會(huì)創(chuàng)建新的字符串;連接變量的話則涉及StringBuilder等字符串構(gòu)建器的創(chuàng)建,會(huì)在堆內(nèi)存生成新的字符串對(duì)象。
以上就是我們給您帶來(lái)的關(guān)于Java字符串的一些知識(shí)總結(jié)和面試技巧,你學(xué)廢了嗎?
總結(jié)
以上是生活随笔為你收集整理的传一个实体一个string_没想到,一个小小的String还有这么多窍门的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 安卓电子书格式转换(安卓电子书格式)
- 下一篇: 开锁公安局备案查询系统(开锁公安局备案查