你真的瞭解Java中的String嗎(也太菜了)?

背景

我們在開發的時候,在處理字段的時候遇到大量的字符拼接的時候會使用StringBuffer和StringBuild。這是爲什麼呢?那就是因爲String會在每一次創建的時候都會新建一個對象,原來的對象也不會被刪除,還有就是說還有StringBuffer和StringBuild有什麼區別呢?帶着這個疑問我們看一下源碼

源碼閱讀

  1. 我們先看一下String類
//從類上看,他使用了final關鍵字,說明這個類是不可以改變的,且繼承了Serializable,他是可以被序列化的
public final  class String implements java.io.Serializable, Comparable<String>, CharSequence {
    //還有這個存儲我們的字符串的value 也是被final 修飾了所以他這個值是不可以改變的
    private final char value[];

  1. 從上面的源碼看出了String裏面的存儲數據的主類型是不可以變的,那我們在拼接字符串的時候是怎麼實現的呢?那這裏我們就得看一下String的+“”的實現原理
javac Test         編譯文件
javap -c Test   查看虛擬機指令

實驗一:純字符串

public class Test {
    public static void main(String args[]) {
        String str = "a";
    }
}

// 將字符串 a 存入常數池
   0:   ldc     #2; //String a
   // 將引用存放到 1 號局部變量中
   2:   astore_1
   3:   return
實驗二:純字符串相加

public class Test {
    public static void main(String args[]) {
        String str = "a" + "b";
    }
}


   // 將字符串 ab 壓入常數池
   0:   ldc     #2; //String ab
   2:   astore_1
   3:   return

實驗二能夠非常明顯地看出,編譯器在編譯時產生的字節碼已經將 "a" + "b" 優化成了 "ab",
同理多個字符串的相加也會被優化處理,須要注意的是字符串常量相加。

實驗三:字符串與自己主動提升常量相加
public class Test {
    public static void main(String args[]) {
        String str = "a" + (1 + 2);
    }
}
// 將字符串 a3 壓入常數池
   0:   ldc     #2; //String a3
   2:   astore_1
   3:   return

通過虛擬機指令能夠看出,1 + 2 自己主動提升後的常量與字符串常量,虛擬機也會對其進行優化。
  1. 從上面的分析我們看出的是在+的時候沒有引用變量的時候所以會在編譯階段後將java中的String直接拼接好直接存入數據庫中去(因爲引用變量會在運行時期纔會給值)。所以說在運行時是怎樣的呢? 這個時候我們再看一下StringBuilder在編譯後是怎樣的。
String b = new StringBuilder().append("a").append(bb).toString();
  1. 是不是很驚喜,好的那我們來解釋一下爲什麼呢?正是因爲String內部的char數組被修飾了final且是一個基本類型,所以說他這個值是不可以用了。所以需要使用我們的StringBuild來實現字符串在動態運行時的拼接。
  2. 那麼我們也就可以解釋,String爲什麼會比直接使用StringBuild而更多的內存了。因爲每一次使用字符串+引用的時候都會進行茶創建StringBuilder對象將原有的StringBuilder對象又拼到現在這個StringBuilder對象。而我們直接使用StringBuilder進行apend的話是在原有的基礎上進行添加的。所以說是這個原因。
  3. 那說到這裏了,那它爲什麼不使用StringBuffer而使用StringBuilder呢?再去看看StringBuffer和StringBuilder的區別是什麼?
// 看到這個類也是不能被繼承的且繼承了AbstractStringBuilder的抽象類
 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
 ………………
 }
    在這裏我們可以看到他是使用了鎖的。
    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    



// 看到這個類我們也可以看到他也繼承了AbstractStringBuilder
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

    /** use serialVersionUID for interoperability */
    static final long serialVersionUID = 4383685877147921099L;

    /**
     * Constructs a string builder with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuilder() {
        super(16);
    }
    // 這也可以看看,他這塊是沒有使用Sychronized這把鎖的
      @Override
    public StringBuilder append(String str) {
    // 這就是重點他也是使用了AbstractStringBuilder中的apend方法。
        super.append(str);
        return this;
    }

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    
  1. 這麼對比下來他兩最大的區別也就是在他的操作方法上一個有鎖一個是沒有鎖的。那麼也就是說爲什麼StringBuilder了呢?從性能的角度來看StringBUilder雖然不是線程3安全的,但是他的性能是優秀的。並且在String拼接引用變量的時候想想也不會出現有併發安全問題的,因爲他是在編譯期生成且不會被併發修改的。所以使用StringBuilder而不使用StringBuffer。

總結

  1. String 是線程安全的,因爲他的變量是不可變的,也就是它是一個imutable類。
  2. String的運行時字符串拼接使用過編譯器翻譯成StringBuilder實現的
  3. StringBuilder和StringBuffer的區別就是一個是線程安全的一個是線程不安全的。原因也就是buffer會在操作數據的方法上加鎖使其同步操作達到線程安全
  4. 其中也和HashMap和HashRTable,ArrayLIst和Vector的區別一樣。

參考鏈接

https://www.cnblogs.com/chenlong-50954265/p/5632275.html
https://blog.csdn.net/weixin_33713707/article/details/85912589

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章