目錄
關於String
String類型的底層代碼是Java所有底層代碼中相對來說比較簡單、好理解。同時,String底層的源碼又是面試官經典的“面試第一題”。“良好的開端就是成功的一半”,因此這個問題回答的好壞會很大程度影響你接下來的面試。我會在這篇博客中梳理幾個面試中頻率較高的知識點。
String內部結構
String內部存儲結構為char數組,有兩個私有變量,一個是char[],哈希值。
具體結構:
class String implements Serializable,Comparable<String>,CharSequence {
//用於存儲字符串的值
private char value[];
//緩字符串的hash code
private int hash;
}
String的構造方法
String的構造方法有四個,前兩個比較常見,參數是String字符串和char[]數組。
后兩個是容易被忽略的小透明,參數是StringBuffer和StringBuilder
1.String為參數的構造方法
//String為參數的構造方法
public String(String original){
this.value = original.value;
this.hash = original.hash;
}
2.char[] 為參數的構造方法
//char[]為參數的構造方法
public String(char value[]){
this.value = Arrays.copyOf(value,value.length);
}
3.StringBuffer為參數的構造方法
//StringBuffer為參數的構造方法
public String(StringBuffer buffer){
synchronized(buffer){
this.value=Array.copyOf(buffer.getValue(),buffer.length())
}
}
4.StringBuilder為參數的構造方法
//StirngBuilder為參數的構造方法
public String(StringBuilder builder){
this.value = Arrays.copyOf(builder.getValue(),builder.length());
}
String中的對比——equals()和compareTo()的對比
equals()方法
String中的equals()方法是重寫了Object類中的equals()方法,其本質是利用了“==”。
equals()方法先檢驗對比雙方的引用地址是否相等(“==”),如果地址相等,對比雙方自然相等,返回true。然后檢驗需要對比的對象是否為String類型(“instanceof”),如果不是則返回false。之后對比兩個字符串的長度是否對等(“==”),最后將兩個字符串都轉化為char[]形式,循環對比每一個字符。
源碼:
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()方法
compareTo()方法不涉及地址的對比,它只是循環比較所有的字符串。當兩個字符串中有任意一個字符不相同的時候,返回兩個字符的差值。如果長度不對等則返回兩個字符串長度的差值。
源碼:
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;
}
小結:String中compareTo()和equals()方法的異同點
不同點:
- equals()可以接收一個Object類型的參數,而compareTo()只能接收String類型的參數
- equals()返回值為Boolean,而compareTo()的返回值則為int
相同點:
- 兩者都可以用於兩個字符串的比較。當equals()方法返回true時,或是compareTo()方法返回0時,則表示兩個字符串完全相同
String的常用方法清單
- indexOf():查詢字符串首次出現的下標位置
- lastIndexOf():查詢字符串最后出現的下標位置
- contains(): 查詢字符串中是否包含另一個字符串
- toLowerCase(): 把字符串全部轉換成小寫
- toUpperCase(): 把字符串全部轉換成大寫
- length(): 查詢字符串的長度
- trim(): 去掉字符串首尾空格
- replace():替換字符串中的某些字符
- split(): 把字符串分割並返回字符串數組
- join(): 把字符串數組轉為字符串
關於equals()方法:“==”和equals()的區別?
“==”:
- 對於基本數據類型來說,是比較兩者的值是否相等
- 對於引用數據類型來說,是用於比較兩者引用地址是否相等
equals():
- String類型的equals()是重寫了Object類中的equals()方法,他的基本實現是靠的“==”
Object類中的equal方法:
public boolean equals(Object obj){
return (this==obj);
}
為什么用final修飾String類?
對於這個問題,Java之父James Gosling在一次記者采訪中說明過,大體的原因如下:
1.安全
迫使String類被設計成不可變類的一個原因就是安全。(在進行系統的校驗工作中,如果設為可變類,就有可能會出現嚴重的系統崩潰問題。)
舉例:字符串常量池
2.高效
高司令是這樣回答的:他會更傾向於使用不可變類(final),因為它能夠緩存結果,當你在傳參時不需要考慮誰會修改它的值。如果是可變類的話,則有可能需要重新拷貝出來一個新值進行傳參,這樣性能上就會有一定的損失。
String和StringBuilder、StringBuffer的區別
首先,考慮到安全和性能問題,String類是不可變的,所以在字符串拼接的時候如果使用String的話性能會很低。因此就需要使用另一個數據類型StringBuffer。
StringBuffer:
- 它提供了append和insert方法可用於字符串的拼接
- 它使用了synchronized來保證線程安全
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的兩種方式:
方式一:
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的優化。
舉例:
String s1 = "ja" + "va";
String s2 = "java";
System.out.println(s1==s2); //true