String類型的底層代碼是Java全部底層代碼中相對來講比較簡單、好理解。同時,String底層的源碼又是面試官經典的「面試第一題」。「良好的開端就是成功的一半」,所以這個問題回答的好壞會很大程度影響你接下來的面試。我會在這篇博客中梳理幾個面試中頻率較高的知識點。java
String內部存儲結構爲char數組,有兩個私有變量,一個是char[],哈希值。
具體結構:面試
class String implements Serializable,Comparable<String>,CharSequence { //用於存儲字符串的值 private char value[]; //緩字符串的hash code private int hash; }
String的構造方法有四個,前兩個比較常見,參數是String字符串和char[]數組。
後兩個是容易被忽略的小透明,參數是StringBuffer和StringBuilder數組
//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=Array.copyOf(buffer.getValue(),buffer.length()) } }
//StirngBuilder爲參數的構造方法 public String(StringBuilder builder){ this.value = Arrays.copyOf(builder.getValue(),builder.length()); }
String中的equals()方法是重寫了Object類中的equals()方法,其本質是利用了「==」。緩存
equals()方法先檢驗對比雙方的引用地址是否相等(「==」),若是地址相等,對比雙方天然相等,返回true。而後檢驗須要對比的對象是否爲String類型(「instanceof」),若是不是則返回false。以後對比兩個字符串的長度是否對等(「==」),最後將兩個字符串都轉化爲char[]形式,循環對比每個字符。安全
源碼:app
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; }
compareTo()方法不涉及地址的對比,它只是循環比較全部的字符串。當兩個字符串中有任意一個字符不相同的時候,返回兩個字符的差值。若是長度不對等則返回兩個字符串長度的差值。jvm
源碼:性能
public int compareTo(String anotherString){ int len1 = value.length; int len2 = anotherString.value.length; //獲取到兩個字符串長度最短的那個int值 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; }
不一樣點:優化
相同點:ui
「==」:
equals():
Object類中的equal方法:
public boolean equals(Object obj){ return (this==obj); }
對於這個問題,Java之父James Gosling在一次記者採訪中說明過,大致的緣由以下:
1.安全
迫使String類被設計成不可變類的一個緣由就是安全。(在進行系統的校驗工做中,若是設爲可變類,就有可能會出現嚴重的系統崩潰問題。)
舉例:字符串常量池
2.高效
高司令是這樣回答的:他會更傾向於使用不可變類(final),由於它可以緩存結果,當你在傳參時不須要考慮誰會修改它的值。若是是可變類的話,則有可能須要從新拷貝出來一個新值進行傳參,這樣性能上就會有必定的損失。
首先,考慮到安全和性能問題,String類是不可變的,因此在字符串拼接的時候若是使用String的話性能會很低。所以就須要使用另外一個數據類型StringBuffer。
StringBuffer:
StringBuffer中append()方法:
public synchronized StringBuffer append(Object obj){ toStringCache = null; super.append(String.valueOf(obj)); return this; } public synchronized StringBuffer append(String str){ toStringCache = null; super.append(String.valueOf(str)); return this; }
可是因爲StringBuffer保證了線程的安全,因此性能上來說 —— 很低。
因而在JDK1.5中推出了線程不安全,可是性能相對而言較高的StringBuilder。其功能和StringBuffer同樣。
咱們先來看看建立String的兩種方式:
方式一:
String s1 = "java"; //直接賦值
方式二:
String s2 = new String("java"); //對象建立
這兩種方法均可以建立字符串,可是兩個方法在JVM的存儲區域大相徑庭
方法一:
jvm會先到字符串常量池中尋找是否有相同的字符串,若是有就返回常量句柄;若是沒有該字符串,就先在常量池中建立此字符串,而後再返回常量句柄。
方法二:
直接在堆中建立此字符串,只有調用intern()纔會放入字符串常量池中。
舉例:
String s1 = new String("java"); String s2 = s1.intern(); String s3 = "java"; System.out.println(s1==s2); //false System.out.println(s2==s3); //true
(s2,s3指向堆中常量池的「java」,s1指向堆中的「java」對象)
== 補充:jdk1.7以後把永生代換成了元空間,把字符串常量池從方法區移到了java堆上 ==
咱們常常會用「+」來拼接兩個字符串。即便是這樣拼接的字符串,在進行比較時,也不會出現和結果相同字符串不對等的狀況。這是編譯器對於String的優化。
舉例:
String s1 = "ja" + "va"; String s2 = "java"; System.out.println(s1==s2); //true