目錄
- 一、是什么?
- 二、區別是?
- 1. 運行速度(執行速度)
- 2. 執行緒安全
- 三、小結
- 四、加餐
一、是什么?
- String 不可變字符序列
String 是字串常量,其物件一旦創建之后該物件是不可更改的, 因此在每次對 String 型別進行改變的時候其實都等同于生成了一個新的 String 物件,然后將指標指向新的 String 物件,所以經常改變內容的字串最好不要用 String ,因為每次生成新物件都會開辟新的記憶體空間,當記憶體中無參考物件多了以后, JVM 的 GC 就會開始作業,那速度一定是相當慢的,對系統性能產生影響,
String 這個類很特殊,特殊在于 JVM 專門為它作了某些處理:在 JVM 中存在一個字串常量池,其中存有很多 String 物件,并且可以被共享使用,當創建一個字串常量時,例如 String str = “Chittyo”; 會首先在字串常量池中查找是否存在相同的字串定義,若已經定義,則直接參考其定義,此時不需要創建新的物件;若沒有定義,則需要創建物件,然后把它加入到字串常量池中,再將他的參考回傳,由于字串是不可變類,一旦創建好了就不可修改,因此字串物件可以被共享而且不會引起程式的混亂,
- StringBuilder 可變字符序列、效率高、非執行緒安全
java.lang.StringBuilder 是 Java 5.0 新增的可變的字符序列,此類提供一個與 StringBuffer 兼容的 API,但不保證同步,該類被設計用作 StringBuffer 的一個簡易替換,用在字串緩沖區被單個執行緒使用的時候(這種情況很普遍),如果可能,建議優先采用該類,因為在大多數實作中,它比 StringBuffer 要快,兩者的方法基本相同,
- StringBuffer 可變字符序列、效率低、執行緒安全
Java.lang.StringBuffer 是執行緒安全的可變字符序列,一個類似于 String 的字串緩沖區,雖然在任意時間點上它都包含某種特定的字符序列,但通過某些方法呼叫可以改變該序列的長度和內容,可將字串緩沖區安全地用于多個執行緒,可以在必要時對這些方法進行同步,因此任意特定實體上的所有操作就好像是以串行順序發生的,該順序與所涉及的每個執行緒進行的方法呼叫順序一致,每個字串生成器都有一定的容量,只要字串生成器包含的字符序列的長度沒有超出此容量,就無需分配新的內容緩沖區,如果內容緩沖區溢位,則此容量自動增大,
StringBuffer 上的主要操作是 append() 和 insert() 方法,可多載這些方法,以接受任意型別的資料,每個方法都能有效地將給定的資料轉換成字串,然后將該字串的字符 追加 or 插入 到字串緩沖區中,append() 方法始終將這些字符添加到緩沖區的末端;而 insert() 方法則在指定的點添加字符,
二、區別是?
主要存在以下兩個方面的區別:運行速度、執行緒安全,
1. 運行速度(執行速度)
運行速度的快慢: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 這個物件被更改了,實則不然,假象而已,

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 快很多,
換個栗子舉一下:
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 類, 下面我們來看下執行緒安全方面的區別,
2. 執行緒安全
StringBuilder 是執行緒不安全的,而 StringBuffer 是執行緒安全的,
如果一個 StringBuffer 物件在字串緩沖區被多個執行緒使用時,StringBuffer 中很多方法可以帶有 synchronized 關鍵字,所以可以保證執行緒是安全的,但 StringBuilder 的方法則沒有該關鍵字,所以不能保證執行緒安全,有可能會出現一些錯誤的操作,所以在多執行緒環境下操作用 StringBuffer,在單執行緒環境下操作,還是建議使用速度比較快的 StringBuilder,
三、小結
- 操作少量的字串資料 用 String;
- 單執行緒下字符緩沖區中的大量操作 用 StringBuilder(推薦使用);
- 多執行緒下字符緩沖區中的大量操作 用 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:創建 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 等,實際上每個基本型別都有他的包裝類)和陣列,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/189909.html
標籤:其他
