String在JAVA中是以final修飾的char數(shù)組實(shí)現(xiàn)的闷串,所以一旦創(chuàng)建了某個(gè)string待牵,不可修改馒过。而且String不能被繼承(final)坚俗。
先來(lái)看第一個(gè)例子:
String b = "b";
String anotherB="b";
String newB = new String("b");
b == anotherB //true
b == newB //false
其中原委:
b
在java加載class的時(shí)候會(huì)被放入方法區(qū)(Method Area)的常量池(Constant Pool)中禽作,之后通過(guò)b
賦值的變量都被當(dāng)作constant
尸昧,直接指向常量池中的內(nèi)存地址,所以b
和anotherB
指向同一個(gè)地址旷偿。
而new String("b")
則創(chuàng)建了一個(gè)instance烹俗,JVM會(huì)在執(zhí)行這條語(yǔ)句的時(shí)候(Runtime)在堆(Heap)中為其分配內(nèi)存,所以和b
指向的不是同一個(gè)地址萍程。
第二個(gè)例子:
String s = "a";
for(int i =0; i < 10; i++)
s+="a";
編譯期間幢妄,上段代碼會(huì)Compiler被優(yōu)化成:
String s = "a";
for(int i =0; i < 10; i++){
StringBuilder sb = new StringBuilder(s);
sb.append("a");
s=sb.toString();
}
創(chuàng)建了10個(gè)StringBuilder
實(shí)例,所以在loop中不要用string進(jìn)行操作茫负,不然會(huì)在Heap中創(chuàng)建大量短生命周期的instance而加重GC的負(fù)擔(dān)蕉鸳。
第三個(gè)例子:
String s = "a" + "b" +"c";
這條語(yǔ)句java創(chuàng)建了多少對(duì)象?
一個(gè)忍法!因?yàn)镃ompiler會(huì)針對(duì)這種語(yǔ)句做優(yōu)化潮尝,所以上述語(yǔ)句等價(jià)于 String s = "abc";
其中abc
被當(dāng)作const處理,加載進(jìn)jvm的時(shí)候放入方法區(qū)常量池饿序,s
指向該const的地址勉失。所以s == "abc"
是true。
另一個(gè)例子:
String a = "a";
String ab = a + "b";
System.out.println(ab == "ab"); //false
因?yàn)閍b賦值中a是變量原探,編譯器不會(huì)針對(duì)優(yōu)化乱凿。
String VS. StringBuilder VS. StringBuffer
StringBuilder是可變的(mutable),非線(xiàn)程安全踢匣。
String是不可變的(immutable)
StringBuffer是可變的告匠,而且線(xiàn)程安全(thread-safe),因?yàn)槠鋬?nèi)的方法都被synchronize關(guān)鍵字修飾,保證每次只有一個(gè)線(xiàn)程操作該對(duì)象。因?yàn)槊看尾僮鞫家燃觤onitor鎖食磕,所以其效率要差于StringBuilder.
String 為什么設(shè)計(jì)成immutable的?
- literal很常用戚哎,設(shè)計(jì)成
final
在JVM加載.class類(lèi)文件的時(shí)候會(huì)把字符串放到方法區(qū)的常量池中裸诽,相同的字符串共用同一個(gè)常量池地址。 - String設(shè)計(jì)成
final
的會(huì)保證hashCode()
也不會(huì)改變型凳,從而可以在String中緩存其計(jì)算的hash code
丈冬,不必每次都重新計(jì)算。 - 如果String是mutable的話(huà)甘畅,其
hashCode()
計(jì)算的結(jié)果也會(huì)變埂蕊,在作為key存取Map
的話(huà)就會(huì)出問(wèn)題:改變之后的string變量作為key
檢索不出之前存入的value
。