String源碼分析

其實String方面的面試題往深了延申的話,還是會延伸到JVM,所以還是希望讀者對JVM有一定的瞭解,這樣更便於理解String的設計。

String源碼分析

String結構

/*
Strings are constant; their values can not be changed after they are created.
Stringbuffers support mutable strings.Because String objects are immutable they can be shared. Forexample:
*/
public final class String implements java.io.Serializable, 
        Comparable<String>, CharSequence 
源碼裏可以看到String被final修飾並繼承了三個接口
源碼註釋也說到字符串是不變的; 它們的值在創建後無法更改.Stringbuffers支持可變字符串。
因爲String對象是不可變的,所以它們可以共享

final

 修飾類:類不可被繼承,也就是說,String類不可被繼承了
 修飾方法:把方法鎖定,以訪任何繼承類修改它的涵義
 修飾遍歷:初始化後不可更改

Comparable和Serializable

Serializable接口裏爲空
Comparable接口裏只有一個public int compareTo(T o);方法
這兩個接口不做敘述.

CharSequence

接口中的方法
    length(): int
    charAt(): char
    subSequence(int,int):CharSwquence 
    toString(): String 
    chars(): IntStream
    codePoints(): IntStream
我們發現這個接口中的方法很少,沒有我們常用的String方法,
那麼它應該是一個通用接口,會有很多實現類,包括StringBuilder和StringBuffer

String構造方法

空參構造

 public String() {
     this.value = "".value;
 }

解析

String str=new String("abc");
1.先創建一個空的String對象
2.常量池中創建一個abc,並賦值給第二個String
3.將第二個String的引用傳遞給第一個String
注:如果常量池中有abc,則不用創建,直接把引用傳遞給第一個String

String類型初始化

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

案例:  String str=new String("str");  

字符數組初始化

public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}

注:將傳過來的char數組copy到value數組裏

字節數組初始化

byte類型的方法有8個,兩個過時的
剩下六個又分爲指定編碼和不指定編碼

不指定編碼

public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }

public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBounds(bytes, offset, length);
        this.value =  StringCoding.decode(charset, bytes, offset, length);
    }

指定編碼

String(byte bytes[], Charset charset)
String(byte bytes[], String charsetName)
String(byte bytes[], int offset, int length, Charset charset)
String(byte bytes[], int offset, int length, String charsetName)

解析

byte是網絡傳輸或存儲的序列化形式,
所以在很多傳輸和存儲的過程中需要將byte[]數組和String進行相互轉化,
byte是字節,char是字符,字節流和字符流需要指定編碼,不然可能會亂碼,
bytes字節流是使用charset進行編碼的,想要將他轉換成unicode的char[]數組,
而又保證不出現亂碼,那就要指定其解碼方法

StringBUilder構造

public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
    
public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }

注:很多時候我們不會這麼去構造,因爲StringBuilder跟StringBuffer有toString方法
如果不考慮線程安全,優先選擇StringBuilder

equals方法

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            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;
    }
String重寫了父類Object的equals方法
    先判斷地址是否相等(地址相等的情況下,肯定是一個值,直接返回true)
    在判斷是否是String類型,不是則返回false
    如果都是String,先判斷長度,
    再比較值,把值賦給char數組,遍歷兩個char數組比較
    

hashcode方法

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
如果String的length==0或者hash值爲0,則直接返回0
如果上述條件不滿足,則通過算法計算hash值

intern方法

public native String intern();

注:方法註釋會有寫到,意思就是調用方法時,
如果常量池有當前String的值,就返回這個值,沒有就加進去,返回這個值的引用
        String str1="a";
        String str2="b";
        String str3="ab";
        String str4 = str1+str2;
        String str5=new String("ab");

        System.out.println(str5==str3);//堆內存比較字符串池
        //intern如果常量池有當前String的值,就返回這個值,沒有就加進去,返回這個值的引用
        System.out.println(str5.intern()==str3);//引用的是同一個字符串池裏的
        System.out.println(str5.intern()==str4);//變量相加給一個新值,所以str4引用的是個新的
        System.out.println(str4==str3);//變量相加給一個新值,所以str4引用的是個新的
        
        false
        true
        false
        false

重點: --兩個字符串常量或者字面量相加,不會new新的字符串,其他相加則是新值,(如 String str5=str1+"b";)

因爲在jvm翻譯爲二進制代碼時,會自動優化,把兩個值後邊的結果先合併,再保存爲一個常量。

String裏還有很多方法,substring,replace等等,我們就不一一舉例了


StringBuilder

StringBuilder和Stringbuffer這兩個類的方法都很想一樣,因此我們就那StringBuilder的源碼作分析
等下再去看三者之間的關係和不同

結構和構造

public final class StringBuilder  extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{
    
    //空構造,初始大小16
    public StringBuilder() {
        super(16);
    }
    
    //給予一個初始化容量
    public StringBuilder(int capacity) {
        super(capacity);
    }
    
    //使用String進行創建
    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }
    
    //String創建和CharSequence類型創建,額外多分配16個字符的空間,
    //然後調用append將參數字符添加進來,(字符串緩衝區)
    public StringBuilder(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }
}

解析

我們可以看到方法內部都是在調用父類的方法,
通過繼承關係,我們是知道它的父類是AbstractStringBuilder,
父類裏實現類Appendable跟CharSequence 接口,所以它能夠跟String相互轉換

父類AbstractStringBuilder

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

解析

父類裏是隻有兩個構造方法,一個爲空實現,一個爲指定字符數組的容量,
如果事先知道String的長度小於16,就可以節省內存空間,
他的數組和String的不一樣,因爲成員變量value數組沒有被final修飾,
所以可以修改他的引用變量的值,即可以引用到新的數組對象,
所以StringBuilder對象是可變的

append

append有很多重載方法,原理都差不多
我們以String舉例

//傳入要追加的字符串
public AbstractStringBuilder append(String str) {
        //判斷字符串是否爲null
        if (str == null)        
            return appendNull();
            
        //不爲null,獲得它的長度
        int len = str.length(); 
        
        //調用方法,把原先長度+追加字符串長度的和傳入方法
        ensureCapacityInternal(count + len);    
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    
    
    
    //判斷是否滿足擴展要求
private void ensureCapacityInternal(int minimumCapacity) {
        // 和-原先字符串長度是否>0  肯定是大於0的
        if (minimumCapacity - value.length > 0)
            //調用複製空間的方法,和當參數
            expandCapacity(minimumCapacity);
    }
    
    
    //開始擴充
void expandCapacity(int minimumCapacity) {
        //先把原先長度複製2倍多2
        int newCapacity = value.length * 2 + 2;
        
        //判斷newCapacity-和是否<0
        if (newCapacity - minimumCapacity < 0)
            //小於0的情況就是你複製的長度不夠,那就把和的長度給複製的長度
            newCapacity = minimumCapacity;
            
            //正常邏輯怎麼着都走不到這一步,新長度肯定是大於0
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        
        //將數組擴容拷貝
        value = Arrays.copyOf(value, newCapacity);
    }
    

insert

insert同樣有很多重載方法,下面以char和String爲例
insert的ensureCapacityInternal(count + 1);和上面一樣,不做講解了

public AbstractStringBuilder insert(int offset, char c) {
        //檢查是否滿足擴充條件
        ensureCapacityInternal(count + 1);
        //拷貝數組
        System.arraycopy(value, offset, value, offset + 1, count - offset);
        //進行復制
        value[offset] = c;
        count += 1;
        return this;
    }
    
    
public AbstractStringBuilder insert(int offset, String str) {
        //判斷要插入的座標是否在字符串內,不再則報數組下標越界
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        //判斷要插入的是否爲null
        if (str == null)
            str = "null";
        //獲得要插入的字符串長度    
        int len = str.length();
        //檢查是否滿足擴充條件
        ensureCapacityInternal(count + len);
        //拷貝數組
        System.arraycopy(value, offset, value, offset + len, count - offset);
        str.getChars(value, offset);
        count += len;
        return this;
    }

StringBuffer

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

跟StringBuilder差不多,只不過在所有的方法上面加了一個同步鎖

equals與==

equals

String類重寫了父類equals的方法
我們先看下父類的

//直接判斷地址
public boolean equals(Object obj) {
        return (this == obj);
    }

再看下String類的equals

public boolean equals(Object anObject) {
    //地址相等肯定爲true,就不用繼續往下走了
        if (this == anObject) {
            return true;
        }
    //地址不相等的情況下,比較兩個字符的內容是否一樣
    //把字符串方法char[]數組裏,遍歷比較
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            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;
    }

==

==比較的是內存地址

基本數據類型比較值
引用數據類型比較地址值
    (對象的引用,在堆空間,String在字符串池,newString在堆空間)

根據下面案例分析一下源碼
創建方式 | 對象個數 | 引用指向

String a="abc"; |1 | 常量池
String b=new String("abc");; |1 | 堆內存 (abc則是複製的常量池裏的abc)
String c=new String() |1 | 堆內存
String d="a"+"bc"; |3 | 常量池(a一次,bc一次,和一次,d指向和)
String e=a+b; |3 | 堆內存

重點--兩個字符串常量或者字面量相加,不會new新的字符串,
變量相加則是會new新的字符串,new出來的都在堆


總結

String被final修飾,一旦創建無法更改,每次更改則是在新創建對象
StringBuilder和StringBuffer則是可修改的字符串

StringBuilder和StringBuffer的區別
    StringBuffer 被synchronized 修飾,同步,線程安全
    StringBuilder並沒有對方法進行加同步鎖,所以是非線程安全的。
    如果程序不是多線程的,那麼使用StringBuilder效率高於StringBuffer。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章