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。