欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中StringBuilder與StringBuffer使用及源碼解讀

 更新時間:2023年05月22日 09:36:50   作者:一一哥Sun  
我們前面學(xué)習(xí)的String就屬于不可變字符串,因為理論上一個String字符串一旦定義好,其內(nèi)容就不可再被改變,但實際上,還有另一種可變字符串,包括StringBuilder和StringBuffer兩個類,那可變字符串有什么特點,又怎么使用呢,接下來就請大家跟我一起來學(xué)習(xí)吧

一. 可變字符串

1. 簡介

在Java中,我們除了可以通過String類創(chuàng)建和處理字符串之外,還可以使用StringBuffer和StringBuilder類來處理字符串。其中,String類定義的字符串內(nèi)容不可變,所以String屬于不可變字符串。而StringBuffer和StringBuilder定義的字符串內(nèi)容可變,這兩者屬于可變字符串,并且StringBuffer和StringBuilder,對字符串的處理效率比String類更高。

2. 使用場景

有的小伙伴可能還是不太理解,字符串的使用并不是很難,咱們直接使用String來操作就可以了,為什么還要搞出來StringBuffer和StringBuilder這兩個類?這不是找麻煩嗎?其實這都是有原因的!

從底層原理來分析,String構(gòu)建的字符串對象,其內(nèi)容理論上是不能被改變的。一旦定義了String對象就無法再改變其內(nèi)容,但很多時候我們還是需要改變字符串的內(nèi)容的,所以String類就存在一定的短板。

另外從應(yīng)用層面來分析,String字符串的執(zhí)行效率其實是比較低的。舉個例子,就比如常見的字符串拼接,很多人喜歡使用“+號”來拼接String字符串。其實如果是操作少量的字符串,使用String還湊活,一旦同時操作的字符串過多,String的效率就極低了。之前曾做過一個關(guān)于10萬個字符串拼接的實驗。同等條件下,利用“+”號進(jìn)行拼接所需要的時間是29382毫秒,利用StringBuffer所需要的時間只有4毫秒,而StringBuilder所用的時間更是只需2毫秒,這效率真是天差地別!

另外我們還可以通過下面這個稍微簡單點的案例,來看看Java底層是如何處理字符串拼接的。

String str = "Hello" + "World";
System.out.println("str=" + str);

相信很多朋友都會用 “+”號 來進(jìn)行字符串拼接,因為覺得該方式簡單方便,畢竟 一 “+” 了事。那么利用 “+”號來拼接字符串是最好的方案嗎?肯定不是的!如果我們使用JAD反編譯工具對上述Java字節(jié)碼進(jìn)行反編譯,你會發(fā)現(xiàn)不一樣的結(jié)果,上述案例反編譯后得到的JAD文件內(nèi)容如下所示:

import java.io.PrintStream;
public class StringTest13{
    public StringTest13(){
    }
    public static void main(String args[]){
        String s = "HelloWorld";
        System.out.println((new StringBuilder()).append("str=").append(s).toString());
    }
}

從反編譯出來的JAD文件中我們可以看出,Java在編譯的時候會把 “+”號操作符替換成StringBuilder的append()方法。也就是說,“+”號操作符在拼接字符串的時候只是一種形式,讓開發(fā)者使用起來比較簡便,代碼看起來比較簡潔,但底層使用的還是StringBuilder操作。

既然 “+”號 的底層還是利用StringBuilder的append()方法操作,那么我們?yōu)槭裁床恢苯邮褂肧tringBuilder呢?你說對吧?而且當(dāng)我們需要操作大量的字符串時,更不推薦使用String,比如:

String str = "";
for (int i = 0; i < 10000; i++) {
    str = str + "," + i;
}

上面這段代碼,雖然可以實現(xiàn)字符串的拼接,但是在該循環(huán)中,每次循環(huán)都會創(chuàng)建一個新的字符串對象,然后扔掉舊的字符串。如果是10000次循環(huán),就會執(zhí)行10000次這樣的操作。而這些操作中的絕大部分字符串對象都是臨時對象,最終都會被扔掉不用,這就會嚴(yán)重地浪費內(nèi)存,并會嚴(yán)重影響GC垃圾回收的效率。

為了能提高拼接字符串的效率,Java給我們提供了StringBuffer和StringBuilder,它們都是可變對象,可以預(yù)分配緩沖區(qū)。當(dāng)我們往StringBuffer或 StringBuilder 中新增字符時,不會創(chuàng)建新的臨時對象,可以極大地節(jié)省了內(nèi)存。 可以說,好處多多。

那么接下來就帶領(lǐng)各位來學(xué)習(xí)StringBuffer、StringBuilder的用法吧。

二. StringBuffer

1. 簡介

StringBuffer是一種可變的字符串類,即在創(chuàng)建StringBuffer對象后,我們還可以隨意修改字符串的內(nèi)容。每個StringBuffer的類對象都能夠存儲指定容量的字符串,如果字符串的長度超過了StringBuffer對象的容量空間,則該對象的容量會自動擴(kuò)大。

另外我們在使用StringBuffer類時,比如每次調(diào)用toString()方法,都會直接使用緩存區(qū)的toStringCache 值來構(gòu)造一個字符串,這每次都是對StringBuffer對象本身進(jìn)行操作,而不會重新生成一個新對象。所以如果我們需要對大量字符串的內(nèi)容進(jìn)行修改,推薦大家使用StringBuffer。

2. 基本特性

StringBuffer作為一個可變字符串類,具有如下特性:

  • 具有線程安全性:StringBuffer中的公開方法都由synchronized關(guān)鍵字修飾,保證了線程同步;
  • 帶有緩沖區(qū):StringBuffer每次調(diào)用toString()方法時,都會直接使用緩存區(qū)的toStringCache值來構(gòu)造一個字符串;
  • 內(nèi)容可變性:StringBuffer中帶有字符串緩沖區(qū),我們可以通過數(shù)組的復(fù)制來實現(xiàn)內(nèi)容的修改;
  • 自帶擴(kuò)容機(jī)制:StringBuffer可以初始化容量,也可以指定容量,當(dāng)字符串長度超過了指定的容量后,可以通過擴(kuò)容機(jī)制實現(xiàn)長度的變更;
  • 內(nèi)容類型多樣性:StringBuffer中可以存儲多種不同類型的數(shù)據(jù)。

了解了StringBuffer的基本特性之后,請大家跟著小編來學(xué)習(xí)一下StringBuffer的基本用法吧。

3. 基本用法

3.1 常用API方法

StringBuffer作為一個字符串操作類,它有以下幾個需要我們掌握的常用API方法,如下所示:

方法名稱方法作用
StringBuffer()構(gòu)造一個空的字符串緩沖區(qū),并且初始化為 16個字符的容量
StringBuffer(int length)創(chuàng)建一個空的字符串緩沖區(qū),并且初始化為指定長度 length的容量
StringBuffer(String str)創(chuàng)建一個字符串緩沖區(qū),并將其內(nèi)容初始化為指定的字符串內(nèi)容 str,字符串緩沖區(qū)的初始容量為 16 加上字符串 str 的長度
StringBuffer append(String s)將指定的字符串追加到此字符序列后面
StringBuffer reverse()將該字符序進(jìn)行反轉(zhuǎn)
StringBuffer delete(int start, int end)移除該字符串中指定起始位置的子字符串
StringBuffer insert(int offset, int i)將int類型的內(nèi)容插入到該字符串的指定位置上
StringBuffer insert(int offset, String str)將String類型的內(nèi)容插入到字符串的指定位置上
StringBuffer replace(int start, int end, String str)使用給定的新子串,替換字符串中指定起始位置上舊的子串
int capacity()返回當(dāng)前字符串的容量
char charAt(int index)返回字符串中指定索引處的char值。
int indexOf(String str)返回在該字符串中第一次出現(xiàn)指定子串的索引值
int indexOf(String str, int fromIndex)從指定索引處開始,返回在該字符串中第一次出現(xiàn)指定子串的索引值
int lastIndexOf(String str)返回指定子串在此字符串中最后的索引值
int length()返回字符串的長度,即字符個數(shù)
CharSequence subSequence(int start, int end)根據(jù)指定的起、止值,返回一個新的子串
String substring(int start)根據(jù)指定的起始值,返回一個新的子串

3.2 基本案例

知道了這些常用的API方法后,我們再通過一個案例來看看這些方法到底是怎么用的。

/**
 * @author 
 */
public class Demo01 {
    public static void main(String[] args) {
	//創(chuàng)建StringBuffer對象
        StringBuffer sb = new StringBuffer("跟一一哥,");
        //在字符串后面追加新的字符串
        sb.append("學(xué)Java!");
        System.out.println(sb); 
        //刪除指定位置上的字符串,從指定的下標(biāo)開始和結(jié)束,下標(biāo)從0開始
        sb.delete(2, 4);
        System.out.println(sb);//"一哥"
        //在指定下標(biāo)位置上添加指定的字符串
        sb.insert(2, "123");
        System.out.println(sb);//跟一123,學(xué)Java!
        //將字符串翻轉(zhuǎn)
        sb.reverse();
        System.out.println(sb);//!avaJ學(xué),321一跟
        //將StringBuffer轉(zhuǎn)換成String類型
        String s = sb.toString();
        System.out.println(s);
    }
}

3.3 append()用法

在以上幾個方法中,再重點給大家說一下append()追加方法。該方法的作用是追加內(nèi)容到當(dāng)前StringBuffer對象的末尾,類似于字符串的連接。調(diào)用該方法以后,StringBuffer對象的內(nèi)容也會發(fā)生改變。使用該方法進(jìn)行字符串的連接,會比String更加節(jié)約內(nèi)存。我們可以利用append()方法進(jìn)行動態(tài)內(nèi)容的追加,比如進(jìn)行數(shù)據(jù)庫SQL語句的拼接:

/**
 * @author 
 */
public class Demo02 {
    public static void main(String[] args) {
	StringBuffer sb = new StringBuffer();
	String user = "yyg";
	String pwd = "123";
		
	//實現(xiàn)SQL語句的拼接
	sb.append("select * from userInfo where username=")
          .append(user)
	  .append(" and pwd=")
	  .append(pwd);

	System.out.println("sql="+sb.toString());
    }
}

StringBuffer的用法其實很簡單,和String差不多,大家簡單掌握即可。

三. StringBuilder

1. 簡介

要想實現(xiàn)可變字符串的操作,其實還有另一個StringBuilder類,該類是在Java 5中被提出的。它和 StringBuffer的基本用法幾乎是完全一樣的,關(guān)于StringBuilder的用法,不會講解太多。

但StringBuilder和StringBuffer最大的不同在于,StringBuilder的各個方法都不是線程安全的(不能同步訪問),在多線程時可能存在線程安全問題,但StringBuilder的執(zhí)行效率卻比StringBuffer快的多。

實際上大多數(shù)情況下,我們都是在單線程下進(jìn)行字符串的操作,所以使用StringBuilder并不會產(chǎn)生線程安全問題。所以針對大多數(shù)的單線程情況,還是建議大家使用StringBuilder,而不是StringBuffer,除非你們的項目對線程安全有著明確的高要求。

2. 特性

StringBuilder作為可變字符串操作類,具有如下特性:

  • StringBuilder是線程不安全的,但執(zhí)行效率更快;
  • 適用于單線程環(huán)境下,在字符緩沖區(qū)進(jìn)行大量操作的情況。

3. 基本用法

StringBuilder的API方法和基本用法與StringBuffer一樣,此處略過。

四. 擴(kuò)容機(jī)制(重點)

擴(kuò)容機(jī)制應(yīng)該是本篇文章中的一個重難點,所以要結(jié)合源碼,單獨列出一節(jié)給大家仔細(xì)分析一下。

在常規(guī)的用法上面,StringBuffer和StringBuilder基本沒有什么差別。兩者的主要區(qū)別在于StringBuffer是線程安全的,但效率低,StringBuilder是線程不安全的,但效率高。不過在擴(kuò)容機(jī)制上,StringBuffer和StringBuilder是一樣的。所以在這里,就以StringBuffer為例,只給大家分析一個類即可。

1. 繼承關(guān)系

首先我們可以追蹤一下StringBuffer的源碼,看看它繼承自哪個父類。

從上圖可以看出,StringBuffer和StringBuilder其實都是繼承自AbstractStringBuilder,所以StringBuffer與StringBuilder這兩者可以說是“親兄弟”的關(guān)系,它們倆有一個共同的抽象父類AbstractStringBuilder,如下所示:

2. AbstractStringBuilder抽象父類

在之前給大家講解抽象類時就跟大家說過,抽象類可以將多個子類個性化的實現(xiàn),通過抽象方法交由子類來實現(xiàn);而多個子類共性的方法,可以放在父類中實現(xiàn)。StringBuffer和StringBuilder的共同父類AbstractStringBuilder就是一個抽象類,在這個父類中把StringBuffer和StringBuilder的一些共同內(nèi)容進(jìn)行了定義。比如在該類中,就定義了一個定長的字節(jié)數(shù)組來保存字符串,后面當(dāng)我們利用append()方法不斷地追加字符串時,如果該字符串的長度超過了這個數(shù)組的長度,就會利用數(shù)組復(fù)制的方式給該數(shù)組進(jìn)行擴(kuò)容。

3. 容量設(shè)置

另外在前面給大家講解StringBuffer的API方法時,也給大家說過StringBuffer有3個構(gòu)造方法。而無論是哪個構(gòu)造方法都可以設(shè)置存儲容量,即使是默認(rèn)的構(gòu)造方法也會有值為16的存儲容量,如下圖所示:

4. 擴(kuò)容過程(核心)

4.1 StringBuffer#append()方法

雖然StringBuffer有默認(rèn)的容量設(shè)置,也有自定義的容量設(shè)置,但在實際開發(fā)過程中,容量還是有可能不夠用。這時就會根據(jù)追加的字符串長度進(jìn)行動態(tài)擴(kuò)容,那么這個擴(kuò)容過程到底是怎么樣的呢?其實StringBuffer的擴(kuò)容需要利用append()方法作為入口,我們先來看看append()方法的源碼,如下所示:

4.2 AbstractStringBuilder#append()方法

在StringBuffer的append()方法中,你會發(fā)現(xiàn)實際上真正的實現(xiàn)是通過super關(guān)鍵字,在調(diào)用父類的append()方法,所以我們繼續(xù)往下追蹤,此時進(jìn)入到AbstractStringBuilder類中的append()方法中,如下圖所示:

此時我們看到了一個ensureCapacityInternal()方法,從字面意思來理解,該方法是用于確保內(nèi)部容量。傳遞給該方法的個參數(shù)是count+len,也就是 原有字符串的長度+新追加的字符串長度,即append后字符串的總長度。

4.3 ensureCapacityInternal()方法

那么ensureCapacityInternal()接受了新字符串的總長度之后會發(fā)生什么變化呢?我們必須進(jìn)入到ensureCapacityInternal()方法的內(nèi)部來探究一番,源碼如下:

在該方法中,我們首先看到了一個二進(jìn)制位的右移運算。value.length是字符數(shù)組的長度,結(jié)合coder參數(shù)進(jìn)行右移運算,得到字符串的原有容量。這里的coder參數(shù)是一種編碼方式,如果字符串中沒有中文,默認(rèn)是采用Latin1編碼,如果有中文則會采用UTF-16編碼。因為UTF-16編碼中文時需要兩個字節(jié),也就是說,只要字符串中含有中文,value字節(jié)數(shù)組中是每兩位對應(yīng)一個字符。

然后會判斷新追加的字符串長度是否超過了value字節(jié)數(shù)組的長度,如果新字符串的長度大于value字節(jié)數(shù)組的長度,則說明需要給該字節(jié)數(shù)組進(jìn)行擴(kuò)容。接著就會利用用Arrays.copyOf()方法,將當(dāng)前數(shù)組的值拷貝給newCapacity()個長度的新數(shù)組,最后再重新賦值給value字節(jié)數(shù)組。在擴(kuò)容的過程中,主要是利用數(shù)組復(fù)制的方法來實現(xiàn)!

4.4 newCapacity()方法

其實講到現(xiàn)在,關(guān)于StringBuffer的擴(kuò)容,基本原理已經(jīng)給大家講清楚了,但我們還可以繼續(xù)深入看看newCapacity()這個方法的實現(xiàn)過程與返回值,它與數(shù)組擴(kuò)容密切相關(guān)。

該方法的大致作用就是,獲取value數(shù)組的原有長度和待追加的新字符串長度,利用ArraysSupport.newLength()方法計算出擴(kuò)容后新數(shù)組的長度length,并最終返回該length。如果length的值等于Integer的最大值,說明我們傳遞過來的字符串太長了,就會直接觸發(fā)一個內(nèi)存溢出的異常。

4.5 newLength()方法

而ArraysSupport.newLength()方法的內(nèi)部實現(xiàn),主要是利用Math.max()方法實現(xiàn)的,如下所示:

4.6 小結(jié)(重點)

至此,就把StringBuffer的擴(kuò)容過程給大家分析完畢了,最后,再給大家把這個擴(kuò)容的核心思路總結(jié)一下,StringBuffer擴(kuò)容機(jī)制的基本規(guī)則如下:

  • 如果一次追加的字符長度超過了當(dāng)前設(shè)置的容量,則會按照 當(dāng)前容量*2+2 進(jìn)行擴(kuò)容;
  • 如果一次追加的長度不僅超過了初始容量,而且按照 當(dāng)前容量*2+2 擴(kuò)容一次還不夠, 其容量會直接擴(kuò)容到與所添加字符串長度相等的長度 ;
  • 之后如果還要再追加新的字符內(nèi)容,依然會按照 當(dāng)前容量*2+2 進(jìn)行擴(kuò)容。

5. 驗證案例

最后為了驗證上述結(jié)論是否正確,再給大家設(shè)計如下案例,供大家思考驗證。

/**
 * @author
 */
public class Demo03 {
    // 擴(kuò)容機(jī)制
    public static void main(String[] args) {
	//無參構(gòu)造方法,初始容量默認(rèn)為16
	StringBuffer sb = new StringBuffer();
	//使用StringBuffer的capacity()方法查看其當(dāng)前容量
	System.out.println("默認(rèn)初始化容量capacity=" + sb.capacity() + ",默認(rèn)長度length=" + sb.length());

	//一次追加20個字符,因為超過了初始容量,因此會擴(kuò)容16*2+2=34
	sb.append("11111111112222222222");
	System.out.println("擴(kuò)容一次的capacity()=" + sb.capacity() + ",擴(kuò)容一次后的length=" + sb.length());
	StringBuffer sb02 = new StringBuffer();
	//再次添加50個字符,不僅超過了初始容量16,而且按照 當(dāng)前容量*2+2 進(jìn)行擴(kuò)容(34)后,依然存儲不下,
        //則直接將容量擴(kuò)容到新追加的字符串長度50
	sb02.append("11111111112222222222333333333344444444445555555555");	
	System.out.println("再次擴(kuò)容后的capacity="+sb02.capacity()+",再次擴(kuò)容后的長度length():"+sb02.length());
    }
}

從上述實驗的執(zhí)行結(jié)果中,你會發(fā)現(xiàn)StringBuffer與StringBuilder就是按照上述規(guī)則進(jìn)行擴(kuò)容的。

五. 結(jié)語

至此,我們就把字符串相關(guān)的內(nèi)容都學(xué)習(xí)完了,接下來就把今天的重點內(nèi)容給大家總結(jié)一下,尤其是String、StringBuffer與StringBuilder的區(qū)別有哪些。

1. 相同點

String、StringBuffer、StringBuilder三者共同之處,它們都是final類,不允許被繼承,這樣設(shè)計主要是從性能和安全性上考慮的。

2. 不同點

String、StringBuffer、StringBuilder這三個類之間的區(qū)別主要體現(xiàn)在3個方面,即 運行速度、線程安全、功能、可變性 這4個方面。

在運行速度方面: 三者之間的執(zhí)行速度由快到慢為:StringBuilder > StringBuffer > String

在線程安全方面: StringBuilder是線程不安全的,而StringBuffer是線程安全的。 如果一個StringBuffer對象在字符串緩沖區(qū)被多個線程使用,StringBuffer中很多方法都帶有synchronized關(guān)鍵字,可以保證線程是安全的。但StringBuilder的方法中則沒有該關(guān)鍵字,所以不能保證線程安全,有可能在進(jìn)行線程并發(fā)操作時產(chǎn)生一些異常。所以如果要進(jìn)行多線程環(huán)境下的操作,考慮使用StringBuffer;在單線程環(huán)境下,建議使用速度StringBuilder。

在功能方面: String實現(xiàn)了三個接口,即Serializable、Comparable、CarSequence; StringBuilder和StringBuffer實現(xiàn)了兩個接口,Serializable、CharSequence,相比之下String的實例可以通過compareTo方法進(jìn)行比較,其他兩個不可以。

在可變性方面:String字符串是不可變的,StringBuilder與StringBuffer是可變的。

3. 最后總結(jié)一下

String: 適用于少量字符串操作的情況;

StringBuilder:適用于單線程環(huán)境下,在字符緩沖區(qū)進(jìn)行大量操作的情況;

StringBuffer: 適用多線程環(huán)境下,在字符緩沖區(qū)進(jìn)行大量操作的情況;

使用場景 當(dāng)修改字符串的操作比較多時,可以使用StringBuilder或StringBuffer;在要求線程安全的情況下用StringBuffer,在不要求線程安全的情況下用StringBuilder。

以上就是Java中StringBuilder與StringBuffer使用及源碼解讀的詳細(xì)內(nèi)容,更多關(guān)于Java StringBuilder與StringBuffer的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論