1. 程式人生 > >Java——String物件

Java——String物件

前言



實際上任何語言都沒有提供字串這個概念,而是使用字元陣列來描述字串。Java裡面嚴格來說也是沒有字串的,在所有的開發裡面字串的應用有很多,於是Java為了應對便建立了String類這個字串類。使用""定義的內容都是字串,理解Java的String類需要從類的角度和記憶體關係上分析這個類。

下面將介紹:

  • String類物件的兩種例項化方式
  • 使用"=="和equals比較字串是否相等
  • String常量為匿名物件
  • String兩種例項化方式的區別
  • 字串一旦定義則不可變
  • 位元組與字串
  • 字串中的方法分類
  • 過載"+"與StringBuilder
  • StringBuffer與StringBuilder



String類物件的兩種例項化方式


String name1 = "Sakura";    //直接賦值方式
String name2 = new String("Sakura");  //利用構造方法例項化



使用"=="和equals比較字串是否相等



使用"=="比較的是兩個物件在記憶體中的地址是否一致,也就是比較兩個物件是否為同一個物件。

使用equals()方法比較的是物件的值是否相等,name1和name2所指物件的值都是"Sakura"所以輸出為true。



String常量為匿名物件



像"Sakura"這樣的字串不屬於基本資料型別,而是作為String類的
匿名物件
而存在。

驗證"Sakura"字串是否為匿名物件:

"Sakura"可以呼叫String類的方法,由此可見"Sakura"是一個物件。

建立String物件的直接賦值方式相當於將一個匿名物件設定了一個名字。在前篇文章裡我們說直接使用"new 類名稱();"的方法建立的是一個匿名物件,String類的匿名物件卻不是這樣的,String類的匿名物件是由系統自動生成不是由使用者直接建立。

下面會講JVM中關於字串的記憶體分配管理。

Notice

圖中的程式碼實際隱含了一個避免出現NullPointerException的小技巧。

若是我們像下面這樣寫字串比較程式碼:

未知name1是否指向了一個物件,所以會存在丟擲空指標異常的情況。為了避免空指標異常我們就可以將字串常量寫在前面。



兩種例項化方式的區別



直接賦值方式

前面講過直接賦值方式就是將一個字串的匿名物件設定了一個名字。

"=="比較的是兩個物件記憶體地址是否一致,由輸出結果可以看出name1和name2指向了同一塊堆空間。

為什麼不是在堆空間中開闢兩個"Sakura"物件而是讓name1和name2指向同一個物件呢?

這個需要談到JVM的共享設計模式

JVM的底層實現實際上在堆中存在一個物件池(常量池,不一定只儲存String物件),當我們使用直接賦值方式定義String類物件,那麼JVM會將此字串物件使用的匿名物件就是如"Sakura"字串入池儲存。如果後面還有其他String物件採用同樣方式且設定同樣內容時,將不會開闢新的堆空間,而是繼續使用相同的空間。

採用構造方法例項化

String name = new String("Sakura");

分析以上語句開闢空間情況:

開闢了一塊棧記憶體儲存了物件引用; 開闢了兩塊堆空間,一塊在常量池中儲存"Sakura"字串常量另一塊在堆中儲存這個物件。

當堆中的物件若是沒有引用指向就是垃圾物件會被GC清理掉。所以,這種構造方式會造成一塊堆空間的浪費。

若是希望,此方式的物件也可以入池儲存也是有方法的,就是利用String類的intern方法。

public class Test {
    public static void main(String[] args) {
        String name1 = new String("Sakura").intern();   //返回一個匿名物件 name1就指向的是常量池中的"Sakura"
        String name2 = "Sakura";
        System.out.println(name1==name2);
    }
}
/*
output:
true
*/

但是方法真的顯得很麻煩!

總結一下兩種例項化方法的區別:

  • 直接賦值方式:只會開闢一塊堆記憶體空間,並且自動儲存在常量池中,以供我們下次重複使用。
  • 構造方法:會開闢兩塊堆記憶體空間,其中在常量池的會成為垃圾空間。



字串一旦定義便不可變


String name = "Amy";
String name = "Smith";
String name = "Amy" + "Smith";

Java定義了String內容不能被改變。分析堆記憶體,是可以知道字串物件內容的改變是利用了引用關係的變化而實現的。每一次的改變都會產生垃圾空間,所以不要頻繁更改定義好的字串。



位元組與字串



檢視API可以看見有許多關於位元組的方法。位元組使用byte描述,是Java中的基本資料型別之一,使用位元組一般主要用於資料的傳輸或者進行編碼轉換的時候使用。在String中有許多將字串轉換為位元組陣列的操作,目的就是為了傳輸轉換。


字串中的方法分類


在程式開發中對字串的操作是常事的,那麼在Java中對字串操作方法也是有很多的。主要分為下面幾類,關於每種方法的使用可以檢視API,但是最好還是幾乎要掌握完。

  • 比較方法
  • 查詢方法
  • 替換方法
  • 擷取方法
  • 拆分方法



過載"+"與StringBuilder



Java中不允許程式設計師過載任何操作符,但是Java內部過載了兩個用於String類的操作符"+"和"+="。操作符"+"可以用於連線字串,操作符"+="用於將連線後的字串再次賦給原字串引用。

由前面所知,不斷使用"+="連線,會產生很多的中間垃圾物件,而且連線的越多也就越浪費空間和時間。垃圾物件佔用空間,Java垃圾回收器清理越耗時。

雖然使用這種方式連線字串,從分析堆疊圖來看很費空間和耗時。但是,JVM在執行程式會不會給優化呢。我們反編譯下面程式來觀察一下:

javap -c StringContact

public class StringContact {
    public static void main(String[] args) {
        String str1="hello";
        String str2="Sakura"+str1+"!";
        System.out.println(str2);
    }
}
/*
output:
    Sakurahello!
*/

可以看出:編譯器自動引入了java.lang.StringBuilder類。編譯器先自動建立了一個StringBuilder物件,每次字串連線時呼叫StringBuilder的append()方法,呼叫了兩次。最後,呼叫toString()生成最終的字串,存在str2中使用命令astroe_2。

編譯器自主使用StringBuilder類,因為它更高效。StringBuilder物件含有一個緩衝區來處理字串,所以可以修改刪除字串。在上面程式碼中建立了一個StringBuilder物件,連線字串時只是不斷呼叫其append方法,沒有建立反覆建立物件。

使用下面的例子深入看看編譯器的優化程度:

public class CompareStringBuilder {
    public String implicit(String[] fields) {           //使用String隱式的字串連線
        String result="";
        for(int i=0; i<fields.length; i++)
            result += fields[i];
        return result;
    }

    public String explicit(String[] fields) {           //使用StringBuilder的append方法連線字串
        StringBuilder result=new StringBuilder();
        for(int i=0; i<fields.length; i++)
            result.append(fields[i]);
        return result.toString();
    }
}

反編譯上述程式:

若是不滿足Code 8的大於等於迴圈次數的話,那麼Code 5到Code 35就是一個迴圈,並且在每一次迴圈中StringBuilder物件都會被建立。

可以看出這個程式碼只是在最初建立了一次StringBuilder物件,並且在迴圈中是一直使用append()方法修改字串。

以上兩段程式碼可以看出編譯器對我們程式碼的優化程度,字串較簡單時可以直接使用String,若是需要大量連線則需要可考慮StringBuilder類。



StringBuilder與StringBuffer



與String物件比較StringBuffer是一個可變的物件,可以通過其自帶的某些方法改變其值的長度和內容。如使用append()方法追加字串。

同StringBuilder一樣,StringBuffer對字串的修改效率要高於String。

檢視JDK文件可知,StringBuilder是在JAVA 5中提出,與StringBuffer擁有的方法幾乎相似,可以看成StringBuffer一種“替換”形式。二者的主要區別是, StringBuffer是執行緒安全的,而StringBuilder是執行緒不安全的

檢視JDK原始碼,可以發現StringBuffer在其每個方法前都加了synchronized關鍵字(用於多執行緒執行緒同步)

如append方法

@Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

因為StringBuilder是執行緒不安全的,所以一般用在單執行緒,因為其不需要管理執行緒同步這些問題所以速度會比StringBuffer快。



小結



本文主要介紹了:

String物件使用equals的比較物件是否相等以及使用"=="判斷物件是否同一物件;String物件的兩種例項化方式,直接賦值不會產生垃圾空間,並且會存入常量池中,構造方法會產生中間垃圾物件且不會入池;String物件是一個不可變物件,String類物件內容改變是依靠引用關係變化,實際物件並沒有發生任何變化;若是經常改變字串值需要使用StringBuilder或者StringBuffer。

部分內容參考自《Java程式設計思想》(第四版)