String 是字符串常量,其對象一旦建立以後該對象是不可更改的, 所以在每次對 String 類型進行改變的時候其實都等同於生成了一個新的 String 對象,而後將指針指向新的 String 對象,因此常常改變內容的字符串最好不要用 String ,由於每次生成新對象都會開闢新的內存空間,當內存中無引用對象多了之後, JVM 的 GC 就會開始工做,那速度必定是至關慢的,對系統性能產生影響。
String 這個類很特殊,特殊在於 JVM 專門爲它做了某些處理:在 JVM 中存在一個字符串常量池,其中存有不少 String 對象,而且能夠被共享使用。當建立一個字符串常量時,例如 String str = 「Chittyo」; 會首先在字符串常量池中查找是否存在相同的字符串定義,若已經定義,則直接引用其定義,此時不須要建立新的對象;若沒有定義,則須要建立對象,而後把它加入到字符串常量池中,再將他的引用返回。因爲字符串是不可變類,一旦建立好了就不可修改,所以字符串對象能夠被共享並且不會引發程序的混亂。html
java.lang.StringBuilder 是 Java 5.0 新增的可變的字符序列。此類提供一個與 StringBuffer 兼容的 API,但不保證同步。該類被設計用做 StringBuffer 的一個簡易替換,用在字符串緩衝區被單個線程使用的時候(這種狀況很廣泛)。若是可能,建議優先採用該類,由於在大多數實現中,它比 StringBuffer 要快。二者的方法基本相同。java
Java.lang.StringBuffer 是線程安全的可變字符序列。一個相似於 String 的字符串緩衝區。雖然在任意時間點上它都包含某種特定的字符序列,但經過某些方法調用能夠改變該序列的長度和內容。可將字符串緩衝區安全地用於多個線程。能夠在必要時對這些方法進行同步,所以任意特定實例上的全部操做就好像是以串行順序發生的,該順序與所涉及的每一個線程進行的方法調用順序一致。每一個字符串生成器都有必定的容量,只要字符串生成器包含的字符序列的長度沒有超出此容量,就無需分配新的內容緩衝區。若是內容緩衝區溢出,則此容量自動增大。
StringBuffer 上的主要操做是 append() 和 insert() 方法,可重載這些方法,以接受任意類型的數據。每一個方法都能有效地將給定的數據轉換成字符串,而後將該字符串的字符 追加 or 插入 到字符串緩衝區中。append() 方法始終將這些字符添加到緩衝區的末端;而 insert() 方法則在指定的點添加字符。android
主要存在如下兩個方面的區別:運行速度、線程安全。數組
運行速度的快慢:StringBuilder > StringBuffer > String。安全
爲何 String 最慢呢?由於 String 爲字符串常量,而 StringBuilder 和 StringBuffer 均爲字符串變量,即 String 對象一旦建立以後該對象是不可更改的,但後二者的對象是變量,是能夠更改的。
一言不合上代碼,舉個栗子:多線程
String str = "Chitty"; System.out.println(str); str = str + "o"; System.out.println(str);
運行這段代碼,會先輸出 「Chitty」,後輸出 「Chittyo」。看着像是 str 這個對象被更改了,實則否則,假象而已。
app
JVM 對於這幾行代碼是這樣處理的,(嚴謹起見,假設上述代碼中的字符串都是第一次建立,在字符串常量池中找不到),首先在堆內存中新建了一塊內存空間,分配給第一行建立的 String 對象 str,並把 「Chitty」 賦值給 str,而後在第三行中,JVM 在堆內存中又建立了兩分內存空間,用來存放 「o」 和最終的 String 對象 str。因此,第一行的 str 實際上並無被更改,即以前說的 String 對象一旦建立以後就不可更改了。而原來第一行的 str(「Chitty」) 以及 第三行中新建的 「o」 的內存空間,並不會即時就被 JVM 的垃圾回收機制(GC)給回收掉,GC 的時機是 JVM 在某個時候,纔開始執行的,因此並必定會明顯的因爲新開闢內存空間,且回收內存,引發 String 速度變慢。嚴謹來講,在大量的 String 拼接操做出現的時候,JVM 因爲開闢內存空間過多,致使內存緊張,基本實時進行 GC,這樣纔會引發速度變慢。Java 中對 String 對象進行的操做其實是一個不斷建立新的對象而且適時將舊的對象回收的一個過程,這不只是對內存空間的極大浪費,也致使了執行速度緩慢。而 StringBuilder 和 StringBuffer 的對象是變量,可以被屢次修改,且不產生新的對象,即不進行建立和回收的操做,因此速度要比 String 快不少。ide
換個栗子舉一下:函數
String str = "Chitty" + "o"; StringBuilder stringBuilder = new StringBuilder().append("Chitty").append("o"); System.out.println(str); System.out.println(stringBuilder.toString());
這樣輸出結果也是 「Chittyo」 和 「Chittyo」,可是 String 的速度卻比 StringBuilder 的反應速度要快不少,這是由於第 1 行中的操做性能
String str = "Chitty" + "o";
和
String str = "Chittyo";
是徹底同樣的,因此會很快。如若寫成下面這種形式,
String str1 = "Chitty"; String str2 = "o"; String str = str1 + str2;
那麼,JVM 就會像上面說的那樣,不斷的建立、回收對象來進行這個操做了。速度就會很慢。
因爲 StringBuilder 相較於 StringBuffer 有速度優點,因此多數狀況下建議使用 StringBuilder 類。然而在應用程序要求線程安全的狀況下,則必須使用 StringBuffer 類。 下面咱們來看下線程安全方面的區別。
StringBuilder 是線程不安全的,而 StringBuffer 是線程安全的。
若是一個 StringBuffer 對象在字符串緩衝區被多個線程使用時,StringBuffer 中不少方法能夠帶有 synchronized 關鍵字,因此能夠保證線程是安全的。但 StringBuilder 的方法則沒有該關鍵字,因此不能保證線程安全,有可能會出現一些錯誤的操做。因此在多線程環境下操做用 StringBuffer,在單線程環境下操做,仍是建議使用速度比較快的 StringBuilder。
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:建立 String 對象的兩種方式的區別是什麼?
A:首先看一下打印結果:第五行打印 true
;第六行打印 false
。
分析:咱們知道 Java 的 8 種基本數據類型( int, long, short, double, float, byte, char, boolean)用 ==
比較的是變量值,由於他們沒有地址,只有值。而 String 是引用數據類型,==
比較的是兩個引用變量的地址。
strC 和 strD 是 new 出來的兩個徹底不一樣的對象,引用變量的地址不一樣,僅僅是值相等。可類比記憶:兩我的僅僅是名字相同。因此第六行打印 false
。
來看一下,建立新對象的過程:
① 執行
String strC = new String("Chittyo");
時,JVM 直接建立一個新的對象並讓strC
指向該對象;
② 執行String strD = new String("Chittyo");
時,JVM 再次建立一個新的對象並讓strD
指向該對象;
③ 因此strC
與strD
指向不一樣的對象,即引用變量的地址不一樣。
那麼 strA ==
strB 嗎?strA、strB 並非經過 new 的方式建立的,因此他們的地址取決於後面所賦的值。Java 中,普通字符串存儲在字符串常量池中,字符串常量池目前位於堆內存中( JDK 1.8,JVM 把字符串常量池移到了堆內存中)。
再來瞄一眼,直接賦值過程:
① 執行
String strA = "Chittyo";
後,JVM 在字符串常量池中開闢空間存放一個「Chittyo」
字符串空間並讓strA
指向該對象。
② 執行String strB = "Chittyo";
時,JVM 會先檢查字符串常量池中是否已經存在了一內容爲"Chittyo"
的空間,若是存在就直接讓strB
指向該空間,不然就會在開闢一個新的空間存放該字符串。
③ 因此建立strB
的時候,由於字符串常量池中已經有字符串"Chittyo"
,因此直接讓strB
指向該空間。至關於:String strB = strA;
因此,從賦值方面來看,此時的 strA ==
strB 是成立的,比較的是字符串常量池裏的值。(字符串常量池在堆內存中)
引用類型指向一個對象,指向對象的變量是引用變量。這些變量在聲明時被指定爲一個特定的類型。變量一旦聲明後,類型就不能被改變了。對象、數組都是引用數據類型。全部引用類型的默認值都是 null
。一個引用變量能夠用來引用任何與之兼容的類型。
通常對於對象,比較值是否相等的時候,都是經過覆寫 equals() 方法和 hashCode() 方法來比較的。String 類型比較不一樣對象內容是否相同,應該用 equals()
,由於 ==
用於比較引用數據類型和基本數據類型時具備不一樣的功能。
==
用於基本數據類型的比較,判斷引用是否指向堆內存的同一塊地址。equals()
用於判斷兩個變量是不是被同一個對象引用,即堆中的內容是否相同,返回值爲布爾類型。
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
巧記:==
用來比較棧內存中的值,equals()
用來比較堆內存中的值。
JVM 把內存劃分紅兩種:一種是棧內存,一種是堆內存。
① 在函數中定義的一些基本數據類型的變量和對象的引用變量(變量名)都在函數的棧內存中分配。
② 當在一段代碼塊定義一個變量時,Java 就在棧中爲這個變量分配內存空間,當超過變量的做用域後,Java 會自動釋放掉爲該變量所分配的內存空間,該內存空間能夠當即被另做他用。
③ 堆內存用來存放由 new 建立的對象(包括由基本類型包裝起來的類:Integer、String、Double 等,實際上每一個基本類型都有他的包裝類)和數組。結尾
本文到這裏就結束了,感謝看到最後的朋友,都看到最後了,點個贊再走啊,若有不對之處還請多多指正。關注我帶你解鎖更多精彩內容