String的那些事

  • 2020 年 3 月 17 日
  • 筆記

String的特點?

通過查看String類的源碼我們得知:String類被final關鍵字修飾,這即是說明String類的特點就是:字元串對象一旦被初始化就不會被改變。注意:此處是字元串對象而不是字元串引用。也即是說:

  String s = "abc";   // s引用變數指向了值為"abc"的對象    s = "cba";    // s引用變數又指向了值為"cba"的對象,但是上面值為"abc"的對象的值並未改變  

那麼String類被final修飾有什麼好處呢?第一個好處是安全,因為final保證不管怎樣操作,它的值都是不變的;第二個好處就是高效,因為只有String類是不可變類時,我們才能實現字元串常量池。試想如果String類是可變類,當多個字元串引用變數指向同一個字元串對象時,字元串對象改變後就會引起多個字元串引用變數內容的改變,這顯然不符合我們的預期。我們可以通過下面的程式碼來驗證字元串常量池的存在:

  class Demo {      public static void main(String[] args) {          String s1 = "abc";          String s2 = "abc";            Demo d1 = new Demo();          Demo d2 = new Demo();          System.out.println(s1 == s2);     // 1          System.out.println(d1 == d2);      }  }  

我們知道:如果是兩個引用變數使用"=="進行比較,那麼比較的是兩個對象的地址值,1處的程式碼輸出結果為"true",說明s1引用變數和s2引用變數指向的是同一個對象,也就驗證了字元串常量池的存在。字元串常量池其實就是字元串的一個緩衝區,而"快取"可以提高系統性能,那麼即是說字元串常量池的使用可以提高系統性能。常量池的特點就在於:如果池中沒有則創建,如果池中有就直接使用池中的。

String內部實際存儲結構為char數組。下面為jdk1.8版本的String源碼:

  public final class String          implements java.io.Serializable, Comparable<String>, CharSequence {        // 用於存儲字元串的值      private final char value[];        // 用於快取字元串的hashcode      private int hash; // Default to 0        // else code      // ... ...  }  

我們需要注意編譯器會對String做一些優化,比如下列程式碼:

  class Demo {      public static void main(String[] args) {          String s1 ="Ja"+"va";          String s2 = "Java";          System.out.println(s1 == s2);      }  }  

String的構造方法?

String是一個類,所以我們除了使用String s1 = "abc"方式創建字元串對象之外,還可以通過String類的構造方法進行創建。通過查看jdk文檔我們發現String類
有下面這樣的構造函數:

我們需要注意這兩種創建方式的區別:

  class Demo {      public static void main(String[] args) {          String s1 = newString("Java");   // 1          String s2 = s1.intern();         // 2          String s3 = "Java";              // 3          System.out.println(s1 == s2);          System.out.println(s2 == s3);      }  }  

1處程式碼的含義是:在堆記憶體中使用new的方式創建了一個字元串對象並把地址值賦給了引用變數s1,並且該對象在創建的時候接收了一個字元串對象;而2處程式碼的含義是:在堆記憶體中創建一個變數s2,如果調用intern()才會把此字元串保存到字元串常量池中;3處程式碼的含義是:在字元串常量池中創建了一個值為"Java"的字元串對象,並把該對象的地址值賦給了引用變數s3;所以3處的程式碼最終只創建了一個對象,而1處的程式碼有可能創建一個(字元串常量池中有字元串對象"Java")也有可能創建兩個(字元串常量池中沒有字元串對象"Java")。這三個引用變數在JVM中存儲的位置如下:

我們可以看到引用變數s1和引用變數s2的內容其實是相等的,都是"Java",於是如果我們只想比較兩者的內容時,就可以使用equals()。

  class Demo {      public static void main(String[] args) {          String s1 = "abc";          String s2 = new String("abc");            System.out.println(s1 == s2);          System.out.println(s1.equals(s2));      }  }  

這裡需要注意,Object類中equals()的源碼是下面這樣的:

  public boolean equals(Object obj) {      return (this == obj);   // 比較的還是對象的地址值  }  

而String類對該方法進行了覆蓋,源碼是這樣的:

  public boolean equals(Object anObject) {      // 對象引用相同直接返回 true      if (this == anObject) {          return true;      }        // 判斷需要對比的值是否為String類型,如果不是則直接返回false      if (anObject instanceof String) {          String anotherString = (String)anObject;          int n = value.length;          if (n == anotherString.value.length) {              // 把兩個字元串都轉換為 char 數組對比              char v1[] = value;              char v2[] = anotherString.value;              int i = 0;                // 循環比對兩個字元串的每一個字元              while (n-- != 0) {                    // 如果其中有一個字元不相等就 true false,否則繼續對比                  if (v1[i] != v2[i])                      return false;                  i++;              }              return true;          }      }      return false;  }  

String類有幾個很常用的構造方法,如下:

  // char[]為參數的構造方法  public String(char value[]) {      this.value = Arrays.copyOf(value, value.length);  }    // byte[]為參數的構造方法  public String(byte bytes[]) {      this(bytes, 0, bytes.length);  }    // string為參數的構造方法  public String(String original) {      this.value = original.value;      this.hash = original.hash;  }    // StringBuffer為參數的構造方法  public String(StringBuffer buffer) {      synchronized(buffer) {          this.value = Arrays.copyOf(buffer.getValue(), buffer.length());      }  }    // StringBuilder為參數的構造方法  public String(StringBuilder builder) {      this.value = Arrays.copyOf(builder.getValue(), builder.length());  }  

String的常用方法?

通過查看jdk文檔,我們可以看到String類有很多方法,將其中常用的方法分為下面幾類:

  • 用於獲取:
  1.1 獲取字元串中字元的個數(長度):          int length();  1.2 根據位置獲取字元:          char charAt(int index);  1.3 根據字元獲取在字元串中的第一次出現的位置:          int indexOf();    // 注意此方法的有多種重載形式  
  • 用於轉換
  1.1 將字元串變成字元串數組(字元串的切割):          String[]  split(String regex);   // 注意此方法涉及到正則表達式  1.2 將字元串中的字母轉成大小寫:          String toUpperCase():大寫          String toLowerCase():小寫  1.3 將字元串中的內容進行替換:          String replace();  1.4 將字元串兩端的空格去除:          String trim();             
  • 用於判斷
  1.1 判斷字元串中是否包含指定字元串:          boolean contains(string str);  
  • 用於比較
  1.1 比較兩個字元串:          int compareTo(String anotherString)    // 下面為compareTo方法的源碼  public int compareTo(String anotherString) {      int len1 = value.length;      int len2 = anotherString.value.length;      int lim = Math.min(len1, len2);      char v1[] = value;      char v2[] = anotherString.value;        int k = 0;        // 對比每一個字元      while (k < lim) {          char c1 = v1[k];          char c2 = v2[k];            // 如果字元不相等就返回差值          if (c1 != c2) {              return c1 - c2;          }          k++;      }      return len1 - len2;  }  

可以看出compareTo()和equals()都是用於比較兩個字元串的,並且當equals()返回true或者是compareTo()返回0時,則表示兩個字元串完全相同。這兩個方法的區別在於:compareTo()接收的是String類型的參數,而equals()可以接收一個Object類型的參數;compareTo()的返回值為int,而equals()返回值為Boolean。