String,String Builder,String Buffer-原始碼

ML李嘉圖發表於2021-08-03

String

String是一個很普通的類

原始碼分析

//該值用於字元儲存
private final char value[];

//快取字串的雜湊碼
private int hash;// Default to 0

//這個是一個建構函式
//把傳遞進來的字串物件value這個陣列的值,
//賦值給構造的當前物件,hash的處理方式也一樣。

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
}



//String的初始化有很多種
//空引數初始化
//String初始化
//字元陣列初始化
//位元組陣列初始化
//通過StringBuffer,StringBuilder構造


問題: 我現正在準備構造一個String的物件,那original這個物件又是從何而來?是什麼時候構造的呢?

測試一下:

public static void main(String[] args) {
        String str = new String("zwt");
        String str1 =  new String("zwt");
}

在Java中,當值被雙引號引起來(如本示例中的"abc"),JVM會去先檢檢視一看常量池裡有沒有abc這個物件,

如果沒有,把abc初始化為物件放入常量池,如果有,直接返回常量池內容。

Java字串兩種宣告方式在堆記憶體中不同的體現

為了避免重複的建立物件,儘量使用String s1 ="123" 而不是String s1 = new String("123"),因為JVM對前者給做了優化。

常用的API

System.out.println(str.isEmpty());//判斷是不是空字串
System.out.println(str.length());//獲取字串長度
System.out.println(str.charAt(1));//獲取指定位置的字元
System.out.println(str.substring(2, 3));//擷取指定區間字串
System.out.println(str.equals(str1));//比較字串

isEmpty()

    public boolean isEmpty() {
        return value.length == 0;
    }

length()

    public int length() {
        return value.length;
    }

charAt()

    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

substring()

    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        //如果擷取的開始範圍剛好是0並且結束範圍等於陣列的長度,直接返回當前物件,
        //否則用該陣列和傳入的開始範圍和結束範圍重新構建String物件並返回。
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

equals()

    public boolean equals(Object anObject) {
        //如果是同一個引用,直接返回true
        if (this == anObject) {
            return true;
        }
        //判斷是否是String
        if (anObject instanceof String) {
            //判斷長度是否一致
            String anotherString = (String)anObject;
            int n = value.length;
            //判斷char[]裡面的每一個值是否相等
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

equals()與“==”

這兩者之間沒有必然的聯絡,

在引用型別中,"=="是比較兩個引用是否指向堆記憶體裡的同一個地址(同一個物件),

equals是一個普通的方法,該方法返回的結果依賴於自身的實現

intern()

public native String intern();

//如果常量池中有當前String的值,就返回這個值,如果沒有就加進去,返回這個值的引用,

一些基礎

Java基本資料型別和引用型別

Java中一共有四類八種基本資料型別, 除掉這四類八種基本型別,其它的都是物件,也就是引用型別。

基本資料型別
浮點型別 float double
字元型 char
邏輯型 boolean
整型 byte short int long

Java自動裝箱/拆箱

Integer 裡面我們曾經說過得 valueOf (), 這個加上valueOf方法的過程,就是Java中經常說的裝箱過程。

在JDK1.5中,給這四類八種基本型別加入了包裝類 。

第一類:整型
byte Byte
short Short
int Integer
long Long

第二類:浮點型
float Float
double Double

第三類:邏輯型
boolean Boolean

第四類:字元型
char Character

將int的變數轉換成Integer物件,這個過程叫做裝箱,

反之將Integer物件轉換成int型別值,這個過程叫做拆箱。

以上這些裝箱拆箱的方法是在編譯成class檔案時自動加上的,不需要程式設計師手工介入,因此又叫自動裝箱/拆箱。

用處:

1、物件是對現實世界的模擬 。

2、為泛型提供了支援。

3、提供了豐富的屬性和API

public static void main(String[] args) {
        int int1 = 180;
        Integer int2 = new Integer(180);
}

表現如下圖:

StringBuilder

StringBuilder類被 final 所修飾,因此不能被繼承。

StringBuilder類繼承於 AbstractStringBuilder類。

實際上,AbstractStringBuilder類具體實現了可變字元序列的一系列操作,

比如:append()、insert()、delete()、replace()、charAt()方法等。

值得一提的是,StringBuffer也是繼承於AbstractStringBuilder類。

StringBuilder類實現了2個介面:

Serializable 序列化介面,表示物件可以被序列化。

CharSequence 字元序列介面,提供了幾個對字元序列進行只讀訪問的方法,

比如:length()、charAt()、subSequence()、toString()方法等。

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

定義的常量

//toString 返回的最後一個值的快取。每當修改 StringBuffer 時清除。 
private transient char[] toStringCache;

AbstractStringBuilder

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    //value 用來儲存字元序列中的字元。value是一個動態的陣列,當儲存容量不足時,會對它進行擴容。
    char[] value;

    /**
     * The count is the number of characters used.
     */
    //count 表示value陣列中已儲存的字元數。
    int count;
    
    

構造方法

public StringBuilder() {
    super(16);
}

public StringBuilder(int capacity) {
    super(capacity);
}

public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
}

public StringBuilder(CharSequence seq) {
    this(seq.length() + 16);
    append(seq);
}

// AbstractStringBuilder.java
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}

StringBuilder類提供了4個構造方法。構造方法主要完成了對value陣列的初始化。

其中:

  1. 預設構造方法設定了value陣列的初始容量為16。
  2. 第2個構造方法設定了value陣列的初始容量為指定的大小。
  3. 第3個構造方法接受一個String物件作為引數,設定了value陣列的初始容量為String物件的長度+16,並把String物件中的字元新增到value陣列中。
  4. 第4個構造方法接受一個CharSequence物件作為引數,設定了value陣列的初始容量為CharSequence物件的長度+16,並把CharSequence物件中的字元新增到value陣列中。

append()方法

有多種實現,一般的順序為:

append() ----> ensureCapacityInternal() 確保value陣列有足夠的容量 ----> newCapacity()新的容量

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }



    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        
        //擴容引數
        
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

在上面程式碼中可以看到具體的擴容規則是 * 2 + 2

StringBuffer

基本就是加了一個synchronized的StringBuilder。

StringBuilder 和 StringBuffer 適用的場景是什麼?

stringbuffer固然是執行緒安全的,stringbuffer固然是比stringbuilder更慢,固然,在多執行緒的情況下,理論上是應該使用執行緒安全的stringbuffer的。

實際上基本沒有什麼地方顯示你需要一個執行緒安全的string拼接器 。

stringbuffer基本沒有適用場景,你應該在所有的情況下選擇使用stringbuiler,除非你真的遇到了一個需要執行緒安全的場景 。

如果你遇見了,,,

stringbuffer的執行緒安全,僅僅是保證jvm不丟擲異常順利的往下執行而已,它可不保證邏輯正確和呼叫順序正確。大多數時候,我們需要的不僅僅是執行緒安全,而是鎖。

最後,為什麼會有stringbuffer的存在,如果真的沒有價值,為什麼jdk會提供這個類?

答案太簡單了,因為最早是沒有stringbuilder的,sun的人不知處於何種考慮,決定讓stringbuffer是執行緒安全的,

於是,在jdk1.5的時候,終於決定提供一個非執行緒安全的stringbuffer實現,並命名為stringbuilder。

順便,javac好像大概也是從這個版本開始,把所有用加號連線的string運算都隱式的改寫成stringbuilder,

也就是說,從jdk1.5開始,用加號拼接字串已經幾乎沒有什麼效能損失了。

擴充套件小知識

Java9改進了字串(包括String、StringBuffer、StringBuilder)的實現。

在Java9以前字串採用char[]陣列來儲存字元,因此字串的每個字元佔2位元組,

而Java9的字串採用byte[]陣列再加一個encoding-flag欄位來儲存字元,因此字串的每個字元只佔1位元組。

所以Java9的字串更加節省空間,字串的功能方法也沒有受到影響。

參考連結

https://zhuanlan.zhihu.com/p/28216267

https://blog.csdn.net/u012317510/article/details/83721250

https://www.zhihu.com/question/20101840

相關文章