Java字符串String那些事
引言
眾所周知在java里面除了8種基本數(shù)據(jù)類型的話,還有一種特殊的類型String,這個類型是我們每天搬磚都基本上要使用它。
?String 類型可能是 Java 中應(yīng)用最頻繁的引用類型,但它的性能問題卻常常被忽略。高效的使用字符串,可以提升系統(tǒng)的整體性能。當(dāng)然,要做到高效使用字符串,需要深入了解其特性。
?
String類
我們可以看下String類的源碼:
public?final?class?String
????implements?java.io.Serializable,?Comparable<String>,?CharSequence?{
????/**?The?value?is?used?for?character?storage.?*/
????private?final?char?value[];
從源碼上我們是不是可以發(fā)現(xiàn)String類是被final關(guān)鍵字所修飾的,String類的數(shù)據(jù)是通過char[] 數(shù)組來存儲的。數(shù)組也是被final修飾的所以String 對象是不可被更改的。接下來我們再看看String的一些方法:像concat、replace、substring等都是返回了一個新的new String感興趣的可以去看看String的一些常見方法。當(dāng)我們執(zhí)行這些方法之后最原始的字符串是沒有改變的,都是返回新的字符串。
?public?static?void?main(String[]?args)?{
????????String?str?=?new?String("java金融");
????????String?str1?=?str.substring(0,?4);
????????String?str2?=?str.concat("公眾號");
????????String?str3?=?str.replace("java金融",?"關(guān)注:【java金融】");
????????//?還有其他的方法
????????System.out.println(str1);
????????System.out.println(str2);
????????System.out.println(str3);
????????System.out.println(str);
????}
輸出結(jié)果
java
java金融公眾號
關(guān)注:【java金融】
java金融
所以我們只要記住一點:“String對象一旦被創(chuàng)建就是固定不變的了,
對String對象的任何改變都不影響到原對象,相關(guān)的任何change操作都會生成新的對象”。
字符串常量池
在JVM中,為了減少字符串對象的重復(fù)創(chuàng)建,維護(hù)了一塊特殊的內(nèi)存空間,這塊內(nèi)存就被稱為全局字符串常量池(string pool也有叫做string literal pool)。
字符串常量池的位置
字符串常量池所在的位置也是跟不同的jdk版本有關(guān)系的。
在 JDK6及之前字符串常量池存放在方法區(qū), 此時hotspot虛擬機(jī)對方法區(qū)的實現(xiàn)為永久代。在 JDK7字符串常量池被從方法區(qū)拿到了堆中, 這里沒有提到運行時常量池,也就是說字符串常量池被單獨拿到堆,運行時常量池剩下的東西還在方法區(qū), 也就是hotspot中的永久代。在 JDK8 hotspot移除了永久代用元空間(Metaspace)取而代之, 這時候字符串常量池還在堆里只不過把方法區(qū)的實現(xiàn)從永久代變成了元空間(Metaspace) 。
String# intern
?String::intern()是一個本地方法,它的作用是如果字符串常量池中已經(jīng)包含一個等于此String對象的字符串,則返回代表池中這個字符串的String對象的引用;否則,會將此String對象包含的字符串添加到常量池中,并且返回此String對象的引用。
?
上述定義出自《深入理解Java虛擬機(jī):JVM高級特性與最佳實踐(第3版)》我們知道了這個 String::intern()這個方法的作用下面來看幾道并沒有什么用的題目看看你是否都能夠回答對?
????????String?str2?=?new?String("java")?+?new?String("金融");?//?1
?????????str2.intern();?//?2
?????????String?str1?=?"java金融";?//?3
?????????System.out.println(str2?==?str1);
這個代碼在JDK6中輸出結(jié)果是false,在jdk7輸出是true。為何會因為不同的jdk版本輸出結(jié)果不一樣,因為不同版本字符串常量池的位置發(fā)生了變化。下面來分析下為何會產(chǎn)生這種差異。字符串雖然不屬于基本數(shù)據(jù)類型但是它也可以想基本類型一樣,直接通過字面量來賦值,同時也是可以通過new 來生成字符串對象。通過字面量賦值的方式和new 的方式 生成字符串還是有區(qū)別的。
字面量賦值:通過字面量賦值(使用雙引號聲明出來的 String)會先去常量池中查找是否已經(jīng)有相同的字符串,如果已經(jīng)存在棧中的引用直接指向該字符串,如果不存在就在常量中生成一個字符串再將棧中的引用指向該字符串。new的方式創(chuàng)建:而通過new的方式創(chuàng)建字符串時,就直接在堆中生成一個字符串的對象棧中的引用指向該對象。對于堆中的字符串對象,可以通過intern() 方法來將字符串添加的常量池中,并返回指向該常量的引用。jdk6結(jié)果是false,是因為常量池是在永久代的Perm區(qū)和java堆是兩個區(qū)域。所以兩個區(qū)域的對象地址比較是不同的。JDK7結(jié)果是true, 這個原因主要是從JDK 7及以后,HotSpot將常量池從永久代移到了堆,正因為如此,JDK7及以后的intern方法在實現(xiàn)上發(fā)生了比較大的改變,JDK7及以后,intern方法還是會先去查詢常量池中是否有已經(jīng)存在,如果存在,則返回常量池中的引用,這一點與之前沒有區(qū)別,區(qū)別在于如果在常量池找不到對應(yīng)的字符串則不會再將字符串拷貝到常量池,而只是在常量池中生成一個對原字符串的引用。所以為什么返回true 是因為執(zhí)行完標(biāo)號為1的時候常量池中沒有"「java金融」"對象的,接下來標(biāo)號為2的時候 會在常量池生成一個“「java金融」”的對象會直接存一個對堆中“「java金融」”的引用,標(biāo)號為3:進(jìn)行字面量賦值的時候常量池已經(jīng)存在了所以直接返回該引用。所以都是指向堆中的字符串返回true。「如果把3行代碼放到第一行上面結(jié)果又不一樣了,感興趣的可以動手試一試并且分析下原因哦。」
string 常見性能優(yōu)化
使用+號拼接字符串
字符串拼接是我們平時在代碼中使用最頻繁的了。
+號拼接靜態(tài)字符串
???String?str?=?"關(guān)注"+"公眾號:"+"java金融";
我們可以通過反編譯查看下上述代碼:
?public?static?void?main(java.lang.String[]);
????descriptor:?([Ljava/lang/String;)V
????flags:?ACC_PUBLIC,?ACC_STATIC
????Code:
??????stack=1,?locals=2,?args_size=1
?????????0:?ldc???????????#2??????????????????//?String?關(guān)注公眾號:java金融
?????????2:?astore_1
?????????3:?return
??????LineNumberTable:
????????line?11:?0
????????line?12:?3
}
我們可以發(fā)現(xiàn)編譯器直接幫我們優(yōu)化了,直接生成了一個字符串“關(guān)注公眾號:java金融” 并沒有生成中間變量的String實例。如果我們上述代碼稍微變化下
???public?static?void?main(String[]?args)?{
????????String?str?="關(guān)注";
????????String?str1?=?str?+?"公眾號:java金融";
????}
?stack=2,?locals=3,?args_size=1
?????????0:?ldc???????????#2??????????????????//?String?關(guān)注
?????????2:?astore_1
?????????3:?new???????????#3??????????????????//?class?java/lang/StringBuilder
?????????6:?dup
?????????7:?invokespecial?#4??????????????????//?Method?java/lang/StringBuilder."":()V
????????10:?aload_1
????????11:?invokevirtual?#5??????????????????//?Method?java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
????????14:?ldc???????????#6??????????????????//?String?公眾號:java金融
????????16:?invokevirtual?#5??????????????????//?Method?java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
????????19:?invokevirtual?#7??????????????????//?Method?java/lang/StringBuilder.toString:()Ljava/lang/String;
????????22:?astore_2
????????23:?return
??????LineNumberTable:
????????line?11:?0
????????line?12:?3
????????line?13:?23
從反編譯代碼中我們會發(fā)現(xiàn)生成了StringBuilder對象來進(jìn)行追加。
所以 String+拼接變量的時候底層是通過StringBuilder來實現(xiàn)的,我們循環(huán)操作拼接字符串的時候也應(yīng)當(dāng)使用StringBuilder替代+,否則的話每一次循環(huán)都會創(chuàng)建 一個StringBuilder對象。對于靜態(tài)字符串的拼接操作,Java在編譯時會進(jìn)行徹底的優(yōu)化,會把多個拼接字符串在編譯時合成一個單獨的長字符串。
常見字符串經(jīng)典面試題
關(guān)于字符串最常見的面試題,面試寶典常見的題目。「String s = new String("xyz")」 創(chuàng)建了多少個實例?一般的回答都會是2個,(一個是“xyz”,一個是指向“xyz”的引用對象s) 答案并沒有那么簡單哦,可以看看大佬的回答還是非常精彩的。連接地址https://www.iteye.com/blog/rednaxelafx-774673(文末第一個參考地址)
結(jié)束
由于自己才疏學(xué)淺,難免會有紕漏,假如你發(fā)現(xiàn)了錯誤的地方,還望留言給我指出來,我會對其加以修正。 如果你覺得文章還不錯,你的轉(zhuǎn)發(fā)、分享、贊賞、點贊、留言就是對我最大的鼓勵。 感謝您的閱讀,十分歡迎并感謝您的關(guān)注。
https://www.iteye.com/blog/rednaxelafx-774673 https://www.zhihu.com/question/36908414/answer/69724311 https://www.cnblogs.com/paddix/p/5326863.html https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html
????????????????????????
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)、等等。
獲取方式:點“在看”,關(guān)注公眾號并回復(fù) 666?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)




