目錄
一赛糟、是什么?
- String 不可變字符序列
String 是字符串常量砸逊,其對(duì)象一旦創(chuàng)建之后該對(duì)象是不可更改的璧南, 因此在每次對(duì) String 類型進(jìn)行改變的時(shí)候其實(shí)都等同于生成了一個(gè)新的 String 對(duì)象,然后將指針指向新的 String 對(duì)象痹兜,所以經(jīng)常改變內(nèi)容的字符串最好不要用 String 穆咐,因?yàn)槊看紊尚聦?duì)象都會(huì)開辟新的內(nèi)存空間,當(dāng)內(nèi)存中無引用對(duì)象多了以后字旭, JVM 的 GC 就會(huì)開始工作,那速度一定是相當(dāng)慢的崖叫,對(duì)系統(tǒng)性能產(chǎn)生影響遗淳。
String 這個(gè)類很特殊,特殊在于 JVM 專門為它作了某些處理:在 JVM 中存在一個(gè)字符串常量池心傀,其中存有很多 String 對(duì)象屈暗,并且可以被共享使用。當(dāng)創(chuàng)建一個(gè)字符串常量時(shí)脂男,例如 String str = “Chittyo”; 會(huì)首先在字符串常量池中查找是否存在相同的字符串定義养叛,若已經(jīng)定義,則直接引用其定義宰翅,此時(shí)不需要?jiǎng)?chuàng)建新的對(duì)象弃甥;若沒有定義,則需要?jiǎng)?chuàng)建對(duì)象汁讼,然后把它加入到字符串常量池中淆攻,再將他的引用返回阔墩。由于字符串是不可變類,一旦創(chuàng)建好了就不可修改瓶珊,因此字符串對(duì)象可以被共享而且不會(huì)引起程序的混亂啸箫。
- StringBuilder 可變字符序列、效率高伞芹、非線程安全
java.lang.StringBuilder 是 Java 5.0 新增的可變的字符序列忘苛。此類提供一個(gè)與 StringBuffer 兼容的 API,但不保證同步唱较。該類被設(shè)計(jì)用作 StringBuffer 的一個(gè)簡(jiǎn)易替換扎唾,用在字符串緩沖區(qū)被單個(gè)線程使用的時(shí)候(這種情況很普遍)。如果可能绊汹,建議優(yōu)先采用該類稽屏,因?yàn)樵诖蠖鄶?shù)實(shí)現(xiàn)中,它比 StringBuffer 要快西乖。兩者的方法基本相同狐榔。
- StringBuffer 可變字符序列、效率低获雕、線程安全
Java.lang.StringBuffer 是線程安全的可變字符序列薄腻。一個(gè)類似于 String 的字符串緩沖區(qū)。雖然在任意時(shí)間點(diǎn)上它都包含某種特定的字符序列届案,但通過某些方法調(diào)用可以改變?cè)撔蛄械拈L(zhǎng)度和內(nèi)容庵楷。可將字符串緩沖區(qū)安全地用于多個(gè)線程楣颠【∨Γ可以在必要時(shí)對(duì)這些方法進(jìn)行同步,因此任意特定實(shí)例上的所有操作就好像是以串行順序發(fā)生的童漩,該順序與所涉及的每個(gè)線程進(jìn)行的方法調(diào)用順序一致弄贿。每個(gè)字符串生成器都有一定的容量,只要字符串生成器包含的字符序列的長(zhǎng)度沒有超出此容量矫膨,就無需分配新的內(nèi)容緩沖區(qū)差凹。如果內(nèi)容緩沖區(qū)溢出,則此容量自動(dòng)增大侧馅。
StringBuffer 上的主要操作是 append() 和 insert() 方法危尿,可重載這些方法,以接受任意類型的數(shù)據(jù)馁痴。每個(gè)方法都能有效地將給定的數(shù)據(jù)轉(zhuǎn)換成字符串谊娇,然后將該字符串的字符 追加 or 插入 到字符串緩沖區(qū)中。append() 方法始終將這些字符添加到緩沖區(qū)的末端弥搞;而 insert() 方法則在指定的點(diǎn)添加字符邮绿。
二渠旁、區(qū)別是?
主要存在以下兩個(gè)方面的區(qū)別:運(yùn)行速度船逮、線程安全顾腊。
1. 運(yùn)行速度(執(zhí)行速度)
運(yùn)行速度的快慢:StringBuilder > StringBuffer > String。
為什么 String 最慢呢挖胃?因?yàn)?String 為字符串常量杂靶,而 StringBuilder 和 StringBuffer 均為字符串變量,即 String 對(duì)象一旦創(chuàng)建之后該對(duì)象是不可更改的酱鸭,但后兩者的對(duì)象是變量吗垮,是可以更改的。
一言不合上代碼凹髓,舉個(gè)栗子:
String str = "Chitty";
System.out.println(str);
str = str + "o";
System.out.println(str);
運(yùn)行這段代碼烁登,會(huì)先輸出 “Chitty”,后輸出 “Chittyo”蔚舀《祝看著像是 str 這個(gè)對(duì)象被更改了,實(shí)則不然赌躺,假象而已狼牺。
JVM 對(duì)于這幾行代碼是這樣處理的,(嚴(yán)謹(jǐn)起見礼患,假設(shè)上述代碼中的字符串都是第一次創(chuàng)建是钥,在字符串常量池中找不到),首先在堆內(nèi)存中新建了一塊內(nèi)存空間缅叠,分配給第一行創(chuàng)建的 String 對(duì)象 str悄泥,并把 “Chitty” 賦值給 str,然后在第三行中肤粱,JVM 在堆內(nèi)存中又創(chuàng)建了兩份內(nèi)存空間码泞,用來存放 “o” 和最終的 String 對(duì)象 str。所以狼犯,第一行的 str 實(shí)際上并沒有被更改,即之前說的 String 對(duì)象一旦創(chuàng)建之后就不可更改了领铐。而原來第一行的 str(“Chitty”) 以及 第三行中新建的 “o” 的內(nèi)存空間悯森,并不會(huì)即時(shí)就被 JVM 的垃圾回收機(jī)制(GC)給回收掉,GC 的時(shí)機(jī)是 JVM 在某個(gè)時(shí)候绪撵,才開始執(zhí)行的瓢姻,所以并一定會(huì)明顯的由于新開辟內(nèi)存空間,且回收內(nèi)存音诈,引起 String 速度變慢幻碱。嚴(yán)謹(jǐn)來說绎狭,在大量的 String 拼接操作出現(xiàn)的時(shí)候,JVM 由于開辟內(nèi)存空間過多褥傍,導(dǎo)致內(nèi)存緊張儡嘶,基本實(shí)時(shí)進(jìn)行 GC,這樣才會(huì)引起速度變慢恍风。Java 中對(duì) String 對(duì)象進(jìn)行的操作實(shí)際上是一個(gè)不斷創(chuàng)建新的對(duì)象并且適時(shí)將舊的對(duì)象回收的一個(gè)過程蹦狂,這不僅是對(duì)內(nèi)存空間的極大浪費(fèi),也導(dǎo)致了執(zhí)行速度緩慢朋贬。而 StringBuilder 和 StringBuffer 的對(duì)象是變量凯楔,能夠被多次修改,且不產(chǎn)生新的對(duì)象锦募,即不進(jìn)行創(chuàng)建和回收的操作摆屯,所以速度要比 String 快很多。
換個(gè)栗子舉一下:
String str = "Chitty" + "o";
StringBuilder stringBuilder = new StringBuilder().append("Chitty").append("o");
System.out.println(str);
System.out.println(stringBuilder.toString());
這樣輸出結(jié)果也是 “Chittyo” 和 “Chittyo”糠亩,但是 String 的速度卻比 StringBuilder 的反應(yīng)速度要快很多虐骑,這是因?yàn)榈?1 行中的操作
String str = "Chitty" + "o";
和
String str = "Chittyo";
是完全一樣的,所以會(huì)很快削解。如若寫成下面這種形式富弦,
String str1 = "Chitty";
String str2 = "o";
String str = str1 + str2;
那么,JVM 就會(huì)像上面說的那樣氛驮,不斷的創(chuàng)建腕柜、回收對(duì)象來進(jìn)行這個(gè)操作了。速度就會(huì)很慢矫废。
由于 StringBuilder 相較于 StringBuffer 有速度優(yōu)勢(shì)盏缤,所以多數(shù)情況下建議使用 StringBuilder 類。然而在應(yīng)用程序要求線程安全的情況下蓖扑,則必須使用 StringBuffer 類唉铜。 下面我們來看下線程安全方面的區(qū)別。
2. 線程安全
StringBuilder 是線程不安全的律杠,而 StringBuffer 是線程安全的潭流。
如果一個(gè) StringBuffer 對(duì)象在字符串緩沖區(qū)被多個(gè)線程使用時(shí),StringBuffer 中很多方法可以帶有 synchronized 關(guān)鍵字柜去,所以可以保證線程是安全的灰嫉。但 StringBuilder 的方法則沒有該關(guān)鍵字,所以不能保證線程安全嗓奢,有可能會(huì)出現(xiàn)一些錯(cuò)誤的操作讼撒。所以在多線程環(huán)境下操作用 StringBuffer,在單線程環(huán)境下操作,還是建議使用速度比較快的 StringBuilder根盒。
三钳幅、小結(jié)
- 操作少量的字符串?dāng)?shù)據(jù) 用 String;
- 單線程下字符緩沖區(qū)中的大量操作 用 StringBuilder(推薦使用)炎滞;
- 多線程下字符緩沖區(qū)中的大量操作 用 StringBuffer敢艰。
四、加餐
String strA = "Chittyo";
String strB = "Chittyo";
String strC = new String("Chittyo");
String strD = new String("Chittyo");
System.out.println(strA == strB);
System.out.println(strC == strD);
Q:創(chuàng)建 String 對(duì)象的兩種方式的區(qū)別是什么厂榛?
A:首先看一下打印結(jié)果:第五行打印 true
盖矫;第六行打印 false
。
分析:我們知道 Java 的 8 種基本數(shù)據(jù)類型( int, long, short, double, float, byte, char, boolean)用 ==
比較的是變量值击奶,因?yàn)樗麄儧]有地址辈双,只有值。而 String 是引用數(shù)據(jù)類型柜砾,==
比較的是兩個(gè)引用變量的地址湃望。
strC 和 strD 是 new 出來的兩個(gè)完全不同的對(duì)象,引用變量的地址不同痰驱,僅僅是值相等证芭。可類比記憶:兩個(gè)人僅僅是名字相同担映。所以第六行打印 false
废士。
來看一下,創(chuàng)建新對(duì)象的過程:
① 執(zhí)行
String strC = new String("Chittyo");
時(shí)蝇完,JVM 直接創(chuàng)建一個(gè)新的對(duì)象并讓strC
指向該對(duì)象官硝;
② 執(zhí)行String strD = new String("Chittyo");
時(shí),JVM 再次創(chuàng)建一個(gè)新的對(duì)象并讓strD
指向該對(duì)象短蜕;
③ 所以strC
與strD
指向不同的對(duì)象氢架,即引用變量的地址不同。
那么 strA ==
strB 嗎朋魔?strA岖研、strB 并不是通過 new 的方式創(chuàng)建的,所以他們的地址取決于后面所賦的值警检。Java 中孙援,普通字符串存儲(chǔ)在字符串常量池中,字符串常量池目前位于堆內(nèi)存中( JDK 1.8扇雕,JVM 把字符串常量池移到了堆內(nèi)存中)赃磨。
再來瞄一眼,直接賦值過程:
① 執(zhí)行
String strA = "Chittyo";
后洼裤,JVM 在字符串常量池中開辟空間存放一個(gè)“Chittyo”
字符串空間并讓strA
指向該對(duì)象。
② 執(zhí)行String strB = "Chittyo";
時(shí),JVM 會(huì)先檢查字符串常量池中是否已經(jīng)存在了一內(nèi)容為"Chittyo"
的空間腮鞍,如果存在就直接讓strB
指向該空間值骇,否則就會(huì)在開辟一個(gè)新的空間存放該字符串。
③ 所以創(chuàng)建strB
的時(shí)候移国,因?yàn)樽址A砍刂幸呀?jīng)有字符串"Chittyo"
吱瘩,所以直接讓strB
指向該空間。相當(dāng)于:String strB = strA;
所以迹缀,從賦值方面來看使碾,此時(shí)的 strA ==
strB 是成立的,比較的是字符串常量池里的值祝懂。(字符串常量池在堆內(nèi)存中)
引用類型指向一個(gè)對(duì)象票摇,指向?qū)ο蟮淖兞渴且米兞俊_@些變量在聲明時(shí)被指定為一個(gè)特定的類型砚蓬。變量一旦聲明后矢门,類型就不能被改變了。對(duì)象灰蛙、數(shù)組都是引用數(shù)據(jù)類型祟剔。所有引用類型的默認(rèn)值都是 null
。一個(gè)引用變量可以用來引用任何與之兼容的類型摩梧。
一般對(duì)于對(duì)象物延,比較值是否相等的時(shí)候,都是通過覆寫 equals() 方法和 hashCode() 方法來比較的仅父。String 類型比較不同對(duì)象內(nèi)容是否相同叛薯,應(yīng)該用 equals()
,因?yàn)?==
用于比較引用數(shù)據(jù)類型和基本數(shù)據(jù)類型時(shí)具有不同的功能驾霜。
==
用于基本數(shù)據(jù)類型的比較案训,判斷引用是否指向堆內(nèi)存的同一塊地址。
equals()
用于判斷兩個(gè)變量是否是被同一個(gè)對(duì)象引用粪糙,即堆中的內(nèi)容是否相同强霎,返回值為布爾類型。
String str1 = new String("Chittyo");
String str2 = new String("Chittyo");
String str3 = str1;
System.out.println(str1 == str2); //false
System.out.println(str1.equals(str2));//true
System.out.println(str1 == str3); //true
System.out.println(str1.equals(str3); //true
巧記:==
用來比較棧內(nèi)存中的值蓉冈,equals()
用來比較堆內(nèi)存中的值城舞。
JVM 把內(nèi)存劃分成兩種:一種是棧內(nèi)存,一種是堆內(nèi)存寞酿。
〖叶帷① 在函數(shù)中定義的一些基本數(shù)據(jù)類型的變量和對(duì)象的引用變量(變量名)都在函數(shù)的棧內(nèi)存中分配。
》サ② 當(dāng)在一段代碼塊定義一個(gè)變量時(shí)拉馋,Java 就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)超過變量的作用域后,Java 會(huì)自動(dòng)釋放掉為該變量所分配的內(nèi)存空間煌茴,該內(nèi)存空間可以立即被另作他用随闺。
③ 堆內(nèi)存用來存放由 new 創(chuàng)建的對(duì)象(包括由基本類型包裝起來的類:Integer蔓腐、String矩乐、Double 等,實(shí)際上每個(gè)基本類型都有他的包裝類)和數(shù)組回论。
結(jié)尾
本文到這里就結(jié)束了散罕,感謝看到最后的朋友,都看到最后了傀蓉,點(diǎn)個(gè)贊再走啊欧漱,如有不對(duì)之處還請(qǐng)多多指正。
關(guān)注我?guī)憬怄i更多精彩內(nèi)容