String,String Builder,String Buffer-源碼


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM