1. 程式人生 > >Java StringBuilder詳解以及與String、StringBuffer區別

Java StringBuilder詳解以及與String、StringBuffer區別

StringBuilder

        就像我們在Java入門教材中寫的,在介紹String的時候寫的是“字串常量”,String實際上就是一個不可變的物件。每次使用String實際上是建立了一個不可變的物件,而改變這個String的值實際上是對這個字串引用更換了新的地址,用圖來表示:

 原來的字串仍然存在在記憶體之中。可以使用程式碼來測試一下:

public class Main {
    public static void main(String[] args) throws Exception{
        String str = "name";
        System.out.println(str.hashCode());
        str = "new name";
        System.out.println(str.hashCode());
    }
}

        那麼對於StringBuilder而言,從命名我們也可以看出來:String的“建設者”,從Java SE 8的API中,官方也給出了這樣的說法:

A mutable sequence of characters. 

StringBuilder是一個字串變數,相比與String,我們在對StringBuilder操作的時候,是對一個變數進行操作,這樣對於JVM而言就會減輕記憶體佔用。StringBuilder引用指向的記憶體地址是不會變化的。

        所以,相比與String,利用StringBuilder來進行字串操作速度要比String快很多,所以例如字串的反轉、插入、拼接等操作,都推薦使用StringBuilder,最後轉成String型別就可以了。

StringBuilder的構造方法

        最常見的是這兩個:

StringBuilder sb = new StringBuilder();

String str = "string";
StringBuilder sb = new StringBuilder(str);

預設的無參構造方法會直接建立一個StringBuilder物件,因為是可變序列,所以自然有容量這個說法,預設情況下是16個字元。也支援將一個String型別的字串轉化為StringBuilder。既然有預設容量,自然也可以自己宣告容量:

StringBuilder(int capacity);

還有一種構造方法比較少用,是將CharSequence型別轉化為StringBuilder,方法也是傳進CharSequence物件即可。

StringBuilder常用方法-append()

        跟String一樣,append適用於字串、字元間的拼接。有多個過載方法適用於很多情況。常用的、看下就知道是幹嘛的方法:

append(char c)
append(char[] str)
append(CharSequence s)
append(CharSequence s, int start, int end)
append(double d)
append(float f)
append(int i)
append(long lng)
append(String str)
append(StringBuffer sb)

就是正常的拼接了,引數指定了如何拼接。append()方法返回值全部是一個StringBuilder型別,返回的是處理以後的StringBuilder物件,但實際上原串也被處理了。

        比較特殊的方法有這些,註釋是其作用:

// 拼接一個boolean型別,其實就是拼接true或者false
append(boolean b)
// 對一個char陣列,指定拼接的開始下標與長度,從offset開始,總共len個字元
append(char[] str, int offset, int len)
// 將Object的引用地址拼接上
append(Object obj)

例子:

sb.append(true);
System.out.println(sb);
sb.append(chs,1,3);
System.out.println(sb);
sb.append(t);
System.out.println(sb);

output:
nametrue
nametruebcd
[email protected]

可以看到,引數是boolean的時候,輸入一個boolean型別,轉化為字串拼接上。而char陣列則是從offset下標開始,拼接len個字元(包括offset)。引數是物件的,則是拼接了該物件的引用地址。這裡我特地全部在sb的基礎上操作,可以看到,sb本身就進行了對應的操作,當然返回值返回的是操作後的StringBuilder物件。

StringBuilder常用方法-insert()

        也是有很多的過載方法:

insert(int offset, boolean b)
insert(int offset, char c)
insert(int offset, char[] str)
insert(int index, char[] str, int offset, int len)
insert(int dstOffset, CharSequence s)
insert(int dstOffset, CharSequence s, int start, int end)
insert(int offset, double d)
insert(int offset, float f)
insert(int offset, int i)
insert(int offset, long l)
insert(int offset, Object obj)
insert(int offset, String str)

這裡有一點,可以看到幾乎所有的方法第一個引數都是一個int型別的數,這表示從StringBuilder的第offset個下標開始插入,與CharSequence物件相關的,dstOffset也是一樣的意思,例如:

StringBuilder sb = new StringBuilder("name");
int a = 123;
sb.insert(1,a);
System.out.println(sb);

output:
n123ame

如果上面append的看完的話,下面insert的基本都能瞭解是什麼作用了,當然這裡有一點,確實是不能指定String型別只插入一部分,例如"abcde",就只能全插進去,不能設定插入其子串。但是對於char陣列可以選擇插入的範圍:

StringBuilder sb = new StringBuilder("name");
char[] chs = {'a','b','c','d','e'};
sb.insert(1,chs,2,3);
System.out.println(sb);

output:
ncdeame

StringBuilder常用方法-其他

        這些都是非常常用的方法:

char charAt(int index)
int length()
StringBuilder reverse()
String substring(int start)
String substring(int start, int end)
StringBuilder delete(int start, int end)
StringBuilder deleteCharAt(int index)
StringBuilder replace(int start, int end, String str)
String toString()

這些是看一眼就知道是啥的方法,在String中也有,都在使用。substring是獲取子串,這裡返回的String型別而不是StringBuilder型別。這裡注意一點,end這個引數跟正常思維是不太一樣的,例如:

StringBuilder sb = new StringBuilder("name");
sb.delete(1,2);
System.out.println(sb);

output:
nme

可以看到,刪除的範圍是從下標start開始(包括start),到下標end結束(不包括end)。其他的方法也是,begin與end都是上述意思。

// 返回當前的容量
int capacity()
// 將StringBuilder的子串複製到char陣列,可自定義從何處開始
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
// 返回第一次出現String的下標索引
int indexOf(String str)
// 同上,但指定從fromIndex下標處開始搜尋
int indexOf(String str, int fromIndex)
// 同上,不過是反向的搜尋
int lastIndexOf(String str)
int lastIndexOf(String str, int fromIndex)
// 指定index處的字元修改為ch
void setCharAt(int index, char ch)
// 設定容量
void setLength(int newLength)

這些方法用法都寫在註釋裡了,indexOf是有著對應的lastIndexOf方法,可以理解為indexOf是從開始進行搜尋,而lastIndexOf則是反向搜尋。利用getChars可以將其中的子串複製到char陣列中。這裡有4個引數,srcBegin、srcEnd明顯是指定複製範圍,dstBegin則是指定了char陣列是從該下標開始複製。

String、StringBuilder、StringBuffer三者比較

        首先看一下這3個類的原始碼中的簽名:

public final class String
extends Object
implements Serializable, Comparable<String>, CharSequence

public final class StringBuilder
extends Object
implements Serializable, CharSequence

public final class StringBuffer
extends Object
implements Serializable, CharSequence

這說明了很大程度上三者的方法等是十分類似的,而實際上String的方法是要多於其他兩者的。值得注意的是:非常常用compareTo,是隻有String有的。其他兩個類需要轉換成String後才能比較。而append方法卻是String沒有的,String只有一個concat()方法,來將一個字串拼接到原串末尾。

        但是在操作的速度上因為String是字串常量,而StringBuffer與StringBuilder都是變數,所以效能要差上一截,String是速度處理最慢的。

        而StringBuffer與StringBuilder最大區別在於執行緒安全上其他基本是一樣的。API中也描述了:

This class provides an API compatible with StringBuffer, but with no guarantee of synchronization. This class is designed for use as a drop-in replacement for StringBuffer in places where the string buffer was being used by a single thread (as is generally the case).

大概意思就是:這倆一樣的,唯一區別在於StringBuilder不能保證同步(執行緒不安全)。StringBuilder是StringBuffer在單執行緒情況下的相容代替者。兩者都是一個緩衝區,在緩衝區上進行操作,區別在於執行緒安全,StringBuffer的大部分方法都能夠使用synchronization來保證執行緒同步(如果瞭解多執行緒會很容易理解)。這樣,在多執行緒的情況下,StringBuffer是明顯安全的,而StringBuilder就不行了。但是也是因為捨棄了安全性,在效能上StringBuilder是要優於StringBuffer的,注意API描述的最後一句:在單執行緒的情況下是更優的,單執行緒下沒有執行緒安全這種說法,那麼StringBuilder效能好、跟StringBuffer一樣,那麼當然要用StringBuilder了。

簡單比較總結

在競賽上:請使用C/C++(= =)為啥要用Java。。。好吧,用StringBuilder,競賽是沒有多執行緒的。

小Demo、Java作業之類的:String。方便快捷,簡單粗暴。

專案:視情況,一般的資料型別之類的當然是String,只有在緩衝區大量操作的情況再考慮其他兩個。

單執行緒且需要在緩衝區上大量操作:StringBuilder。首先要確保效率明顯要高於String才行,其次因為不考慮同步、效率高。

多執行緒且需要在緩衝區上大量操作:StringBuffer。要考慮同步了。