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