Java - String源码解析及常见面试问题
文章目錄
- Pre
- Q1: String 是如何實現的?
- Q2: String 有哪些重要的方法?
- 構造函數
- equals()
- compareTo()
- 【equals() vs compareTo() 】
- 其他重要方法
- Q3: 為什么 String 類型要用 final 修飾
- Q4: == 和 equals 的區別是什么
- Q5: String 和 StringBuilder、StringBuffer 有什么區別
- Q6: String 類型在 JVM中是如何存儲的?編譯器對 String 做了哪些優化
Pre
Java Version : 主流版本JDK 8
Q1: String 是如何實現的?
看到了吧 , 底層存儲是 char 數組
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {// the value is used for character storage 存儲字符串的值private final char value[];// Cache the hash code for the string 緩存字符串的 hash codeprivate int hash; // Default to 0// ...... }Q2: String 有哪些重要的方法?
構造函數
挑幾個比較重要的
// String 為參數的構造方法 public String(String original) {this.value = original.value;this.hash = original.hash; } // char[] 為參數構造方法 public String(char value[]) {this.value = Arrays.copyOf(value, value.length); } // 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()); }這里需要提一下的是: 以 StringBuffer 和 StringBuilder 為參數的構造函數容易被忽略,因為String 、 StringBuffer、StringBuilder 這三種數據類型, 通常都是單獨使用的哇。 知道就行,反正平常也不這么寫
還有其他構造函數 ,大家可以自行看一下
equals()
比較兩個字符串是否相等
來看下源碼
public boolean equals(Object anObject) {// 如果是對象引用,直接返回true if (this == anObject) {return true;}// 類型判斷 如果不是String類型則直接返回 falseif (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) { // 循環比對兩個字符串的每一個字符if (v1[i] != v2[i]) // 如果其中有一個字符不相等就直接返回false,否則繼續對比,直接到結束return false;i++;}return true;}}return false;}equals() 是String 類型重寫的 Object 中的 方法,Object#equals() 方法需要傳遞一個 Object 類型的參數值所以才有了上面的instanceof 類型判斷 。 當判斷參數為 String 類型之后,會循環對比兩個字符串中的每一個字符,當所有字符都相等時返回 true,否則則返回 false。
【Object#equals()】
public boolean equals(Object obj) {return (this == obj); // 僅判斷的對象引用,即比較的是對象在內存中的地址}【instanceof 用法】
Object a= "123"; Object b= 123; System.out.println(a instanceof String); // true System.out.println(b instanceof String); // false另外還有一個 equalsIgnoreCase(), 忽略字符串的大小寫之后進行字符串對比。
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() 方法會循環對比所有的字符,當兩個字符串中有任意一個字符不相同時,則 return c1 - c2。
舉個例子
“53334433”.compareTo(“3”) ----> 2 【取最小長度,第一個字符 5 和 3 比,轉成char 比較, 不相等 返回 5 - 3 = 2】
再來個例子: 兩個字符串分別存儲的是 1 和 2,返回的值是 -1;如果存儲的是 1 和 1,則返回的值是 0 ,如果存儲的是 2 和 1,則返回的值是 1。
還有個compareToIgnoreCase 忽略大小寫后比較兩個字符串。
【equals() vs compareTo() 】
可以看出 compareTo() 方法和 equals() 方法都是用于比較兩個字符串的,但它們有兩點不同:
- equals() 可以接收一個 Object 類型的參數,而 compareTo() 只能接收一個 String 類型的參數
- equals() 返回值為 Boolean,而 compareTo() 的返回值則為 int
它們都可以用于兩個字符串的比較,當 equals() 方法返回 true 時,或者是 compareTo() 方法返回 0 時,則表示兩個字符串完全相同
其他重要方法
- indexOf():查詢字符串首次出現的下標位置
- lastIndexOf():查詢字符串最后出現的下標位置
- contains():查詢字符串中是否包含另一個字符串
- toLowerCase():把字符串全部轉換成小寫
- toUpperCase():把字符串全部轉換成大寫
- length():查詢字符串的長度
- trim():去掉字符串首尾空格
- replace():替換字符串中的某些字符
- split():把字符串分割并返回字符串數組
- join():把字符串數組轉為字符串
Q3: 為什么 String 類型要用 final 修飾
從源碼中可以知道String是final修飾的?
為啥子嘞?
高司令以前回答過: 他會更傾向于使用 final,因為它能夠緩存結果,當你在傳參時不需要考慮誰會修改它的值;如果是可變類的話,則有可能需要重新拷貝出來一個新值進行傳參,這樣在性能上就會有一定的損失。
String 類設計成不可變的另一個原因是安全,當你在調用其他方法時,比如調用一些系統級操作指令之前,可能會有一系列校驗,如果是可變類的話,可能在你校驗過后,它的內部的值又被改變了,這樣有可能會引起嚴重的系統崩潰問題,這是迫使 String 類設計成不可變類的一個重要原因。
總之,使用 final 修飾的第一個好處是安全;第二個好處是高效
我們以JVM中的常量池來舉個例子
String s1 = "java"; String s2 = "java";只有字符串是不可變時,我們才能實現字符串常量池。
字符串常量池可以為我們緩存字符串,這樣的話不用每次都去開辟一塊內存地址存放,自然就提高了運行效率。
如果String是可變的,那字符串常量池就歇菜了。。。。。
Q4: == 和 equals 的區別是什么
【==】
- 對于基本數據類型來說, 比較 “值”是否相等的
- 對于引用類型來說, 比較引用地址是否相同的
Object#equals() 其實就是 ==
public boolean equals(Object obj) {return (this == obj); }String#equal這是重寫了父類Object的equals方法,把它修改成了比較兩個字符串的值是否相等,分析如上。
Q5: String 和 StringBuilder、StringBuffer 有什么區別
簡單來說:
- String 不可變 ,正是因為不可變,所以字符串在拼接時,效率低,所以才有了下面兩個
- StringBuffer 線程安全
- StringBuilder 線程不安全
String 類型是不可變的,所以在字符串拼接的時候如果使用 String 的話性能會很低。
因此我們就需要使用另一個數據類型 StringBuffer,它提供了 append 和 insert 方法可用于字符串的拼接,它使用 synchronized 來保證線程安全
@Override public synchronized StringBuffer append(Object obj) {toStringCache = null;super.append(String.valueOf(obj));return this; } @Override public synchronized StringBuffer insert(int offset, String str) {toStringCache = null;super.insert(offset, str);return this; }因為它使用了 synchronized 來保證線程安全,所以性能不是很高。
于是在 JDK 1.5 就有了 StringBuilder,它同樣提供了 append 和 insert 的拼接方法,但它沒有使用 synchronized 來修飾,因此在性能上要優于 StringBuffer,所以在非并發操作的環境下可使用 StringBuilder 來進行字符串拼接。
@Overridepublic StringBuilder append(String str) {super.append(str);return this;}@Overridepublic StringBuilder insert(int offset, String str) {super.insert(offset, str);return this;}當然了,append 和 insert的方法入參有很多,這里僅僅列舉出了一個,主要是讓你看下 synchronized實現上的區別。
Q6: String 類型在 JVM中是如何存儲的?編譯器對 String 做了哪些優化
String 常見的創建方式有兩種
- new String()
- 直接賦值
直接賦值的方式會先去字符串常量池中查找是否已經有此值,如果有則把引用地址直接指向此值,否則會先在常量池中創建,然后再把引用指向此值;
new String() 一定會先在堆上創建一個字符串對象,然后再去常量池中查詢此字符串的值是否已經存在,如果不存在會先在常量池中創建此字符串,然后把引用的值指向此字符串
舉個例子
String s1 = new String("Java"); String s2 = s1.intern(); String s3 = "Java"; System.out.println(s1 == s2); // ------> false System.out.println(s2 == s3); // ------> trueJDK 1.7 之后把永久代代換成的元空間,把字符串常量池從方法區移到了 Java 堆上
除此之外編譯器還會對 String 字符串做一些優化,例如以下代碼
String s1 = "Ja" + "va"; String s2 = "Java"; System.out.println(s1 == s2);輸出 true
javap -c 反匯編看一下
從編譯代碼 #2 可以看出,代碼 “Ja”+“va” 被直接編譯成了 “Java” ,因此 s1==s2 的結果才是 true,這就是編譯器對字符串優化的結果。
總結
以上是生活随笔為你收集整理的Java - String源码解析及常见面试问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM - 要上线了,JVM参数还没正儿
- 下一篇: APM - Hello Javaage