Java String,StringBuffer與StringBuilder

1 不可變的String

在 Java中,字符串屬於對象,Java提供了 String類 來創建和操作字符串
通過查看 String源碼,可以發現

  • String類是 final類,並且它的成員方法都默認爲final方法
  • String類是通過 char數組來保存字符串的
  • String類的 sub,concat,replace等操作,都不是在原有字符串上進行的,而是重新生成了一個新的字符串,即進行這些操作後,最原始的字符串並沒有被改變
char[] helloArray = {'h','e','l','l','0'};
String helloString = new String(helloString);

String對象是不可變的
String類中每一個看起來會修改String值的方法,實際上都是創建了一個全新的String對象,以包含修改後的字符串的內容。

String str1 = "java";
String str2 = "java";
System.out.println(str1 == str2); // true

在代碼中,可以創建同一個String對象的多個別名,而它們所指向的對象是相同的,一直呆在一個單一的物理位置上,從未動過。
在這裏插入圖片描述

內存分析
String str = "hello"; str = str+"world";;
初始String值爲“hello”,然後在這個字符串後面加上新的字符串“world”,這個過程是需要重新在棧堆內存中開闢內存空間的,最終得到了“hello world”字符串也相應的需要開闢內存空間,這樣短短的兩個字符串,卻需要開闢三次內存空間,不得不說這是對內存空間的極大浪費。

2 StringBuffer和StringBuilder

當對字符串進行修改的時候,需要使用 StringBuffer與 StringBuilder類。
和 String類不同的是,StringBuffer和StringBuilder類的對象能夠被多次修改,並且不產生新的未使用的對象
通過查看源碼發現,StringBuilder和StringBuffer類擁有的成員屬性以及成員方法基本相同,區別是 StringBuffer類的成員方法前面多了一個關鍵字:synchronized。即 StringBuffer是線程安全的,但是 StringBuilder的訪問速度更快,因此,一般用 StringBuilder.

注意:
String 可以賦空值,StringBuilder,StringBuffer不行,它們是對象,必須先 new,獲得具體對象再使用
StringBuffer s = null; //結果警告:Null pointer access: The variable result can only be null at this location
StringBuffer s = new StringBuffer();//StringBuffer對象是一個空的對象

StringBuffer s = new StringBuffer(“abc”);//創建帶有內容的StringBuffer對象,對象的內容就是字符串”

3 String,StringBuffer,StringBuilder三者的執行效率

一般情況下,StringBuilder > StringBuffer > String

不是所有情況下都這樣
比如:String str = “hello”+“world”; 的效率,就比 StringBuilder sBuilder = new StringBuilder().append(“hello”).append(“world”); 要高
因此,這三個類各有利弊,應當根據不同的情況來進行選擇使用

  • 當字符串相加操作或者改動較少的情況下,建議使用 String str = “hello”;這種形式
  • 當字符串相加操作較多的情況下,建議使用 StringBuilder,如果採用了多線程,則使用 StringBuffer
4 String的重載 “+”

在 Java中,唯一被重載的運算符就是用於 String的 "+" 與"+="
除此之外,Java不允許程序員重載其他的運算符

public class StringTest {
    String a = "abc";
    String b = "mongo";
    String info = a + b + 47;
}

String對象是不可變的,所以在上述的代碼過程中可能會是這樣工作的:

(1)"abc" + "mongo"創建新的String對象abcmongo;

(2)"abcmongo" + "47"創建新的String對象abcmongo47;

(3)引用info 指向最終生成的String。

但是這種方式會生成一大堆需要垃圾回收的中間對象,性能相當糟糕。

4.1 編譯器的優化處理

通過反編譯代碼會發現,編譯器自動引入了StringBuilder類
編譯器創建了一個StringBuilder對象,並調用StringBuilder.append()方法,最後調用toString()生成結果,從而避免中間對象的性能損耗。


編譯器優化String對象的連接,而下面這種情況會直接連接作爲常量。

public class StringTest {
    String info = "Andy" + "24" + "Developer";
}
4.2 編譯器的優化是有限度的

性能較低的代碼

public void  implicitUseStringBuilder(String[] values) {
      String result = "";
      for (int i = 0 ; i < values.length; i ++) {
          result += values[i];
      }
      System.out.println(result);
 }

通過反編譯發現,StringBuilder對象創建發生在循環之間,也就是意味着有多少次循環就會創建多少個StringBuilder對象,這樣明顯性能較低。


性能較高的代碼

 public void explicitUseStringBuider(String[] values) {
      StringBuilder result = new StringBuilder();
      for (int i = 0; i < values.length; i ++) {
          result.append(values[i]);
      }
  }

StringBuilder的創建,位於循環之外,所以不會多次創建 StringBuilder


綜上,循環體中需要儘量避免隱式或者顯式創建StringBuilder。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章