Java String 的詳解

結(jié)合代碼來看

 public static void main(String[]args){
/**
*情景一:字符串池
*JAVA虛擬機(JVM)中存在著一個字符串池,其中保存著很多String對象;
*并且可以被共享使用求摇,因此它提高了效率殊者。
*由于String類是final的猖吴,它的值一經(jīng)創(chuàng)建就不可改變挥转。
*字符串池由String類維護,我們可以調(diào)用intern()方法來訪問字符串池绑谣。
*/
String s1="abc";
//↑在字符串池創(chuàng)建了一個對象
String s2="abc";
//↑字符串pool已經(jīng)存在對象“abc”(共享),所以創(chuàng)建0個對象,累計創(chuàng)建一個對象
System.out.println("s1==s2:"+(s1==s2));
//↑true指向同一個對象幌衣,
System.out.println("s1.equals(s2):"+(s1.equals(s2)));
//↑true值相等
//↑------------------------------------------------------over
/**
/*
*情景二:關于new String("")
*/
String s3 = new String("abc");
//↑創(chuàng)建了兩個對象壤玫,一個 "abc" 存放在字符串池中,一個 String 對象存在與堆區(qū)中楚里;
//↑還有一個對象引用s3存放在棧中
String s4 = new String("abc");
//↑字符串池中已經(jīng)存在“abc”對象猎贴,所以只在堆中創(chuàng)建了一個String對象
//↑還有一個對象引用s4存放在棧中
System.out.println("s3==s4:"+(s3==s4));
//↑false s3和s4指向堆區(qū)的不同地址;
System.out.println("s3.equals(s4):"+(s3.equals(s4)));
//↑true s3和s4的值相同
System.out.println("s1==s3:"+(s1==s3));
//↑false存放的地區(qū)不同吝梅,一個棧區(qū)惹骂,一個堆區(qū)
System.out.println("s1.equals(s3):"+(s1.equals(s3)));
//↑true值相同
//↑------------------------------------------------------over
/**
/*情景三:
*由于常量的值在編譯的時候就被確定(優(yōu)化)了。
*在這里右冻,"ab"和"cd"都是常量著拭,因此變量str3的值在編譯時就可以確定。
*這行代碼編譯后的效果等同于:String str1 ="abcd";
*/
String str1 = "ab" + "cd";//1個對象,在編譯時直接創(chuàng)建為"abcd"
String str11="abcd";
System.out.println("str1=str11:"+(str1==str11));
//↑--------true--------同情景一
/**
/*情景四:
*局部變量str2,str3存儲的是存儲兩個拘留字符串對象(intern字符串對象)的地址乳蛾。
*
*第三行代碼原理(str2+str3):
*運行期JVM首先會在堆中創(chuàng)建一個StringBuilder類,
*同時用str2指向的拘留字符串對象完成初始化肃叶,
*然后調(diào)用append方法完成對str3所指向的拘留字符串的合并因惭,
*接著調(diào)用StringBuilder的toString()方法在堆中創(chuàng)建一個String對象,
*最后將剛生成的String對象的堆地址存放在局部變量str3中蹦魔。
*
*而str5存儲的是字符串池中"abcd"所對應的拘留字符串對象的地址。
*str4與str5地址當然不一樣了柱搜。
*
*內(nèi)存中實際上有五個字符串對象:
*三個拘留字符串對象剥险、一個String對象和一個StringBuilder對象。
*/
String str2 = "ab";//1個對象
String str3 = "cd";//1個對象
String str4 = str2+str3;
String str5 = "abcd";
System.out.println("str4=str5:"+(str4==str5));//false
//↑------------------------------------------------------over
/**
/*情景五:
*JAVA編譯器對string+基本類型/常量是當成常量表達式直接求值來優(yōu)化的健爬。
*運行期的兩個string相加么介,會產(chǎn)生新的對象的,存儲在堆(heap)中
*/
Stringstr6="b";
Stringstr7="a"+str6;
Stringstr67="ab";
System.out.println("str7=str67:"+(str7==str67));
//↑str6為變量设拟,在運行期才會被解析久脯。
finalStringstr8="b";
Stringstr9="a"+str8;
Stringstr89="ab";
System.out.println("str9=str89:"+(str9==str89));
//↑str8為常量變量,編譯期會被優(yōu)化
//↑------------------------------------------------------over
}

總結(jié):
1.String類初始化后是不可變的(immutable)
這一說又要說很多帘撰,大家只要知道String的實例一旦生成就不會再改變了摧找,比如說:Stringstr=”kv”+”ill”+”“+”ans”;是有4個字符串常量,首先”kv”和”ill”生成了”kvill”存在內(nèi)存中蹬耘,然后”kvill”又和””生成“kvill“存在內(nèi)存中,最后又和生成了”kvillans”;并把這個字符串的地址賦給了str,就是因為String的”不可變”產(chǎn)生了很多臨時變量惩系,這也就是為什么建議用StringBuffer的原因了,因為StringBuffer是可改變的赃承。
下面是一些String相關的常見問題:
String中的final用法和理解
finalStringBuffera=newStringBuffer(“111”);
finalStringBufferb=newStringBuffer(“222”);
a=b;//此句編譯不通過finalStringBuffera=newStringBuffer(“111”);
a.append(“222”);//編譯通過
可見悴侵,final只對引用的”值”(即內(nèi)存地址)有效拭嫁,它迫使引用只能指向初始指向的那個對象,改變它的指向會導致編譯期錯誤浇借。至于它所指向的對象的變化怕品,final是不負責的。
2.代碼中的字符串常量在編譯的過程中收集并放在class文件的常量區(qū)中闯估,如”123”吼和、”123”+”456”等,含有變量的表達式不會收錄炫乓,如”123”+a。
3.JVM在加載類的時候侠姑,根據(jù)常量區(qū)中的字符串生成常量池箩做,每個字符序列如”123”會生成一個實例放在常量池里,這個實例是不在堆里的船老,也不會被GC圃酵,這個實例的value屬性從源碼的構(gòu)造函數(shù)看應該是用new創(chuàng)建數(shù)組置入123的,所以按我的理解此時value存放的字符數(shù)組地址是在堆里薪韩,如果有誤的話歡迎大家指正。
4.使用String不一定創(chuàng)建對象
在執(zhí)行到雙引號包含字符串的語句時俘陷,如Stringa=“123”,JVM會先到常量池里查找桨菜,如果有的話返回常量池里的這個實例的引用捉偏,否則的話創(chuàng)建一個新實例并置入常量池里。如果是Stringa=“123”+b(假設b是”456”)霞掺,前半部分”123”還是走常量池的路線讹躯,但是這個+操作符其實是轉(zhuǎn)換成[SringBuffer].Appad()來實現(xiàn)的,所以最終a得到是一個新的實例引用骗灶,而且a的value存放的是一個新申請的字符數(shù)組內(nèi)存空間的地址(存放著”123456”)酷麦,而此時”123456”在常量池中是未必存在的。
要注意:我們在使用諸如Stringstr=“abc”沃饶;的格式定義類時糊肤,總是想當然地認為,創(chuàng)建了String類的對象str馆揉。擔心陷阱!對象可能并沒有被創(chuàng)建舷暮!而可能只是指向一個先前已經(jīng)創(chuàng)建的對象噩茄。只有通過new()方法才能保證每次都創(chuàng)建一個新的對象
5.使用newString,一定創(chuàng)建對象
在執(zhí)行Stringa=newString(“123”)的時候沥割,首先走常量池的路線取到一個實例的引用,然后在堆上創(chuàng)建一個新的String實例帜讲,走以下構(gòu)造函數(shù)給value屬性賦值椒拗,然后把實例引用賦值給a:
publicString(Stringoriginal){
intsize=original.count;
char[]originalValue=original.value;
char[]v;
if(originalValue.length>size){
//ThearrayrepresentingtheStringisbiggerthanthenew
//Stringitself.Perhapsthisconstructorisbeingcalled
//inordertotrimthebaggage,somakeacopyofthearray.
intoff=original.offset;
v=Arrays.copyOfRange(originalValue,off,off+size);
}else{
//ThearrayrepresentingtheStringisthesame
//sizeastheString,sonopointinmakingacopy.
v=originalValue;
}
this.offset=0;
this.count=size;
this.value=v;
}
從中我們可以看到,雖然是新創(chuàng)建了一個String的實例玩郊,但是value是等于常量池中的實例的value枉阵,即是說沒有new一個新的字符數(shù)組來存放”123”预茄。
如果是Stringa=newString(“123”+b)的情況,首先看回第4點拙徽,”123”+b得到一個實例后诗宣,再按上面的構(gòu)造函數(shù)執(zhí)行。
6.String.intern()
String對象的實例調(diào)用intern方法后岛心,可以讓JVM檢查常量池篮灼,如果沒有實例的value屬性對應的字符串序列比如”123”(注意是檢查字符串序列而不是檢查實例本身),就將本實例放入常量池髓堪,如果有當前實例的value屬性對應的字符串序列”123”在常量池中存在娘荡,則返回常量池中”123”對應的實例的引用而不是當前實例的引用,即使當前實例的value也是”123”炮沐。
publicnativeStringintern();
存在于.class文件中的常量池央拖,在運行期被JVM裝載鹉戚,并且可以擴充专控。String的intern()方法就是擴充常量池的一個方法;當一個String實例str調(diào)用intern()方法時赢底,Java查找常量池中是否有相同Unicode的字符串常量柏蘑,如果有,則返回其的引用咳焚,如果沒有革半,則在常量池中增加一個Unicode等于str的字符串并返回它的引用;看示例就清楚了

publicstaticvoidmain(String[]args){
Strings0="kvill";
Strings1=newString("kvill");
Strings2=newString("kvill");
System.out.println(s0==s1);//false
System.out.println("**********");
s1.intern();//雖然執(zhí)行了s1.intern(),但它的返回值沒有賦給s1
s2=s2.intern();//把常量池中"kvill"的引用賦給s2
System.out.println(s0==s1);//flase
System.out.println(s0==s1.intern());//true//說明s1.intern()返回的是常量池中"kvill"的引用
System.out.println(s0==s2);//true
}

最后我再破除一個錯誤的理解:有人說延刘,“使用String.intern()方法則可以將一個String類的保存到一個全局String表中六敬,如果具有相同值的Unicode字符串已經(jīng)在這個表中,那么該方法返回表中已有字符串的地址外构,如果在表中沒有相同值的字符串典勇,則將自己的地址注冊到表中”如果我把他說的這個全局的String表理解為常量池的話,他的最后一句話割笙,”如果在表中沒有相同值的字符串伤溉,則將自己的地址注冊到表中”是錯的:

publicstaticvoidmain(String[]args){
Strings1=newString("kvill");
Strings2=s1.intern();
System.out.println(s1==s1.intern());//false
System.out.println(s1+""+s2);//kvillkvill
System.out.println(s2==s1.intern());//true
}

在這個類中我們沒有聲名一個”kvill”常量,所以常量池中一開始是沒有”kvill”的乱顾,當我們調(diào)用s1.intern()后就在常量池中新添加了一個”kvill”常量走净,原來的不在常量池中的”kvill”仍然存在孤里,也就不是“將自己的地址注冊到常量池中”了橘洞。
s1==s1.intern()為false說明原來的”kvill”仍然存在;s2現(xiàn)在為常量池中”kvill”的地址虏等,所以有s2==s1.intern()為true适肠。
StringBuffer與StringBuilder的區(qū)別,它們的應用場景是什么敦跌?
jdk的實現(xiàn)中StringBuffer與StringBuilder都繼承自AbstractStringBuilder沸毁,對于多線程的安全與非安全看到StringBuffer中方法前面的一堆synchronized就大概了解了傻寂。
這里隨便講講AbstractStringBuilder的實現(xiàn)原理:我們知道使用StringBuffer等無非就是為了提高java中字符串連接的效率,因為直接使用+進行字符串連接的話搂誉,jvm會創(chuàng)建多個String對象静檬,因此造成一定的開銷。AbstractStringBuilder中采用一個char數(shù)組來保存需要append的字符串侮腹,char數(shù)組有一個初始大小稻励,當append的字符串長度超過當前char數(shù)組容量時,則對char數(shù)組進行動態(tài)擴展加矛,也即重新申請一段更大的內(nèi)存空間煤篙,然后將當前char數(shù)組拷貝到新的位置,因為重新分配內(nèi)存并拷貝的開銷比較大苛茂,所以每次重新申請內(nèi)存空間都是采用申請大于當前需要的內(nèi)存空間的方式,這里是2倍

StringBuffer始于JDK1.0
StringBuilder始于JDK1.5
從JDK1.5開始妓羊,帶有字符串變量的連接操作(+)侍瑟,JVM內(nèi)部采用的是
StringBuilder來實現(xiàn)的,而之前這個操作是采用StringBuffer實現(xiàn)的涨颜。

我們通過一個簡單的程序來看其執(zhí)行的流程:
publicclassBuffer{
publicstaticvoidmain(String[]args){
Strings1="aaaaa";
Strings2="bbbbb";
Stringr=null;
inti=3694;
r=s1+i+s2;
for(intj=0;i<10;j++){
r+="23124";
}
}
}
使用命令javap-cBuffer查看其字節(jié)碼實現(xiàn):

將清單1和清單2對應起來看庭瑰,清單2的字節(jié)碼中l(wèi)dc指令即從常量池中加載“aaaaa”字符串到棧頂,istore_1將“aaaaa”存到變量1中督暂,后面的一樣穷吮,sipush是將一個短整型常量值(-32768~32767)推送至棧頂,這里是常量“3694”八回。
讓我們直接看到13,1317是new了一個StringBuffer對象并調(diào)用其初始化方法驾诈,2021則是先通過aload_1將變量1壓到棧頂,前面說過變量1放的就是字符串常量“aaaaa”管引,接著通過指令invokevirtual調(diào)用StringBuffer的append方法將“aaaaa”拼接起來闯两,后續(xù)的24~30同理。最后在33調(diào)用StringBuffer的toString函數(shù)獲得String結(jié)果并通過astore存到變量3中噩翠。
看到這里可能有人會說邦投,“既然JVM內(nèi)部采用了StringBuffer來連接字符串了,那么我們自己就不用用StringBuffer屯援,直接用”+“就行了吧!“狞洋。是么?當然不是了庐橙。俗話說”存在既有它的理由”借嗽,讓我們繼續(xù)看后面的循環(huán)對應的字節(jié)碼。
3742都是進入for循環(huán)前的一些準備工作惨寿,37,38是將j置為1裂垦。44這里通過if_icmpge將j與10進行比較,如果j大于10則直接跳轉(zhuǎn)到73缸废,也即return語句退出函數(shù)企量;否則進入循環(huán)亡电,也即4766的字節(jié)碼。這里我們只需看47到51就知道為什么我們要在代碼中自己使用StringBuffer來處理字符串的連接了份乒,因為每次執(zhí)行“+”操作時jvm都要new一個StringBuffer對象來處理字符串的連接或辖,這在涉及很多的字符串連接操作時開銷會很大。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缺谴,一起剝皮案震驚了整個濱河市耳鸯,隨后出現(xiàn)的幾起案子膀曾,更是在濱河造成了極大的恐慌添谊,老刑警劉巖察迟,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異喊废,居然都是意外死亡栗弟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門瓣蛀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惋增,“玉大人改鲫,你說我怎么就攤上這事』鳎” “怎么了缕题?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瘪松。 經(jīng)常有香客問我锨阿,道長,這世上最難降的妖魔是什么壳嚎? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任诬辈,我火速辦了婚禮,結(jié)果婚禮上焙糟,老公的妹妹穿的比我還像新娘。我一直安慰自己缺脉,他們只是感情好悦穿,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布栗柒。 她就那樣靜靜地躺著,像睡著了一般瞬沦。 火紅的嫁衣襯著肌膚如雪逛钻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天芳悲,我揣著相機與錄音边坤,去河邊找鬼。 笑死罢洲,一個胖子當著我的面吹牛文黎,可吹牛的內(nèi)容都是我干的殿较。 我是一名探鬼主播耸峭,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼淋纲!你這毒婦竟也來了劳闹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎本涕,沒想到半個月后业汰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡菩颖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年样漆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片放祟。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖呻右,靈堂內(nèi)的尸體忽然破棺而出跪妥,到底是詐尸還是另有隱情,我是刑警寧澤声滥,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布眉撵,位于F島的核電站,受9級特大地震影響落塑,放射性物質(zhì)發(fā)生泄漏纽疟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一芜赌、第九天 我趴在偏房一處隱蔽的房頂上張望仰挣。 院中可真熱鬧,春花似錦缠沈、人聲如沸膘壶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颓芭。三九已至,卻和暖如春柬赐,著一層夾襖步出監(jiān)牢的瞬間亡问,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工肛宋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留州藕,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓酝陈,卻偏偏與公主長得像床玻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沉帮,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎部分 基礎部分的順序:基本語法锈死,類相關的語法贫堰,內(nèi)部類的語法,繼承相關的語法待牵,異常的語法其屏,線程的語...
    子非魚_t_閱讀 31,581評論 18 399
  • Tip:筆者馬上畢業(yè)了,準備開始 Java 的進階學習計劃缨该。于是打算先從 String 類的源碼分析入手偎行,作為后面...
    石先閱讀 11,990評論 16 58
  • 使用Java語言進行編程,我們每天都要用到String類压彭,但是以前只是拿來就用睦优,并不知道String類的實現(xiàn)原理和...
    lunabird閱讀 429評論 0 1
  • 【七月影語】20170914學習力踐行Day116 1.和舅舅家一起長大的小哥哥視頻,給哥哥背古詩《敕勒歌》《所見...
    暖小柒閱讀 159評論 0 0
  • 火車上睡了一覺壮不,夢見亂七八糟的東西 頭疼
    達浪打啦閱讀 126評論 0 0