1. 程式人生 > 實用技巧 >Java String 的特點是什麼?它有哪些重要的方法?

Java String 的特點是什麼?它有哪些重要的方法?

以主流的 JDK 版本 1.8 來說,String 內部實際儲存結構為 char 陣列,原始碼如下:

publicfinalclassString
implementsjava.io.Serializable,Comparable<String>,CharSequence{
//用於儲存字串的值
privatefinalcharvalue[];
//快取字串的hashcode
privateinthash;//Defaultto0
//......其他內容
}

String原始碼中包含下面幾個重要的方法。

1. 多構造方法

String 字串有以下 4 個重要的構造方法:

//String為引數的構造方法
publicString(Stringoriginal){ this.value=original.value; this.hash=original.hash; } //char[]為引數構造方法 publicString(charvalue[]){ this.value=Arrays.copyOf(value,value.length); } //StringBuffer為引數的構造方法 publicString(StringBufferbuffer){ synchronized(buffer){ this.value=Arrays.copyOf(buffer.getValue(),buffer.length()); } }
//StringBuilder為引數的構造方法 publicString(StringBuilderbuilder){ this.value=Arrays.copyOf(builder.getValue(),builder.length()); }

其中,比較容易被我們忽略的是以 StringBuffer 和 StringBuilder 為引數的建構函式,因為這三種資料型別,我們通常都是單獨使用的,所以這個小細節我們需要特別留意一下。

2. equals() 比較兩個字串是否相等,原始碼如下:

publicbooleanequals(ObjectanObject){
//物件引用相同直接返回true
if(this==anObject){ returntrue; } //判斷需要對比的值是否為String型別,如果不是則直接返回false if(anObjectinstanceofString){ StringanotherString=(String)anObject; intn=value.length; if(n==anotherString.value.length){ //把兩個字串都轉換為char陣列對比 charv1[]=value; charv2[]=anotherString.value; inti=0; //迴圈比對兩個字串的每一個字元 while(n--!=0){ //如果其中有一個字元不相等就truefalse,否則繼續對比 if(v1[i]!=v2[i]) returnfalse; i++; } returntrue; } } returnfalse; }

String 型別重寫了 Object 中的 equals() 方法,equals() 方法需要傳遞一個 Object 型別的引數值,在比較時會先通過 instanceof 判斷是否為 String 型別,如果不是則會直接返回 false,instanceof 的使用如下:

ObjectoString="123";
ObjectoInt=123;
System.out.println(oStringinstanceofString);//返回true
System.out.println(oIntinstanceofString);//返回false

當判斷引數為 String型別之後,會迴圈對比兩個字串中的每一個字元,當所有字元都相等時返回true,否則則返回 false。

還有一個和equals()比較類似的方法 equalsIgnoreCase(),它是用於忽略字串的大小寫之後進行字串對比。

3. compareTo() 比較兩個字串

compareTo()方法用於比較兩個字串,返回的結果為int型別的值,原始碼如下:

publicintcompareTo(StringanotherString){
intlen1=value.length;
intlen2=anotherString.value.length;
//獲取到兩個字串長度最短的那個int值
intlim=Math.min(len1,len2);
charv1[]=value;
charv2[]=anotherString.value;
intk=0;
//對比每一個字元
while(k<lim){
charc1=v1[k];
charc2=v2[k];
if(c1!=c2){
//有字元不相等就返回差值
returnc1-c2;
}
k++;
}
returnlen1-len2;
}

從原始碼中可以看出,compareTo()方法會迴圈對比所有的字元,當兩個字串中有任意一個字元不相同時,則 returnchar1-char2。比如,兩個字串分別儲存的是 1和 2,返回的值是 -1;如果儲存的是 1和 1,則返回的值是 0 ,如果儲存的是 2和 1,則返回的值是 1。

還有一個和compareTo()比較類似的方法 compareToIgnoreCase(),用於忽略大小寫後比較兩個字串。

可以看出compareTo()方法和equals()方法都是用於比較兩個字串的,但它們有兩點不同:

equals()可以接收一個 Object型別的引數,而 compareTo()只能接收一個String型別的引數;
equals()返回值為Boolean,而compareTo()的返回值則為int。
它們都可以用於兩個字串的比較,當equals()方法返回true時,或者是compareTo()方法返回 0時,則表示兩個字串完全相同。

4.其他重要方法

indexOf():查詢字串首次出現的下標位置
lastIndexOf():查詢字串最後出現的下標位置
contains():查詢字串中是否包含另一個字串
toLowerCase():把字串全部轉換成小寫
toUpperCase():把字串全部轉換成大寫
length():查詢字串的長度
trim():去掉字串首尾空格
replace():替換字串中的某些字元
split():把字串分割並返回字串陣列
join():把字串陣列轉為字串

知識擴充套件

1.== 和equals的區別

== 對於基本資料型別來說,是用於比較 “值”是否相等的;而對於引用型別來說,是用於比較引用地址是否相同的。

檢視原始碼我們可以知道 Object 中也有equals() 方法,原始碼如下:

publicbooleanequals(Objectobj){
return(this==obj);
}

可以看出,Object 中的 equals() 方法其實就是 ==,而 String 重寫了 equals() 方法把它修改成比較兩個字串的值是否相等。

publicbooleanequals(ObjectanObject){
//物件引用相同直接返回true
if(this==anObject){
returntrue;
}
//判斷需要對比的值是否為String型別,如果不是則直接返回false
if(anObjectinstanceofString){
StringanotherString=(String)anObject;
intn=value.length;
if(n==anotherString.value.length){
//把兩個字串都轉換為char陣列對比
charv1[]=value;
charv2[]=anotherString.value;
inti=0;
//迴圈比對兩個字串的每一個字元
while(n--!=0){
//如果其中有一個字元不相等就truefalse,否則繼續對比
if(v1[i]!=v2[i])
returnfalse;
i++;
}
returntrue;
}
}
returnfalse;
}

2.final修飾的好處

從String類的原始碼我們可以看出String是被final修飾的不可繼承類,原始碼如下:

publicfinalclassString
    implementsjava.io.Serializable,Comparable<String>,CharSequence{//......}

那這樣設計有什麼好處呢?他會更傾向於使用 final,因為它能夠快取結果,當你在傳參時不需要考慮誰會修改它的值;如果是可變類的話,則有可能需要重新拷貝出來一個新值進行傳參,這樣在效能上就會有一定的損失。

String 類設計成不可變的另一個原因是安全,當你在呼叫其他方法時,比如呼叫一些系統級操作指令之前,可能會有一系列校驗,如果是可變類的話,可能在你校驗過後,它的內部的值又被改變了,這樣有可能會引起嚴重的系統崩潰問題,這是迫使 String 類設計成不可變類的一個重要原因。

總結來說,使用 final 修飾的第一個好處是安全;第二個好處是高效,以 JVM 中的字串常量池來舉例,如下兩個變數:

Strings1="java";
Strings2="java";

只有字串是不可變時,我們才能實現字串常量池,字串常量池可以為我們快取字串,提高程式的執行效率,如下圖所示:

試想一下如果 String 是可變的,那當 s1 的值修改之後,s2 的值也跟著改變了,這樣就和我們預期的結果不相符了,因此也就沒有辦法實現字串常量池的功能了。

3.String 和StringBuilder、StringBuffer的區別

因為 String 型別是不可變的,所以在字串拼接的時候如果使用 String 的話效能會很低,因此我們就需要使用另一個數據型別 StringBuffer,它提供了 append 和 insert 方法可用於字串的拼接,它使用 synchronized 來保證執行緒安全,如下原始碼所示:

@Override
publicsynchronizedStringBufferappend(Objectobj){
toStringCache=null;
super.append(String.valueOf(obj));
returnthis;
}

@Override
publicsynchronizedStringBufferappend(Stringstr){
toStringCache=null;
super.append(str);
returnthis;
}


因為它使用了 synchronized 來保證執行緒安全,所以效能不是很高,於是在 JDK 1.5就有了StringBuilder,它同樣提供了append和insert的拼接方法,但它沒有使用 synchronized 來修飾,因此在效能上要優於 StringBuffer,所以在非併發操作的環境下可使用 StringBuilder 來進行字串拼接。

4.String和JVM

String 常見的建立方式有兩種,new String() 的方式和直接賦值的方式,直接賦值的方式會先去字串常量池中查詢是否已經有此值,如果有則把引用地址直接指向此值,否則會先在常量池中建立,然後再把引用指向此值;而 new String() 的方式一定會先在堆上建立一個字串物件,然後再去常量池中查詢此字串的值是否已經存在,如果不存在會先在常量池中建立此字串,然後把引用的值指向此字串,如下程式碼所示:

Strings1=newString("Java");
Strings2=s1.intern();
Strings3="Java";
System.out.println(s1==s2);//false
System.out.println(s2==s3);//true

它們在 JVM 儲存的位置,如下圖所示:

除此之外編譯器還會對String字串做一些優化,例如以下程式碼:

Strings1="Ja"+"va";
Strings2="Java";
System.out.println(s1==s2);

雖然s1拼接了多個字串,但對比的結果卻是true,我們使用反編譯工具,看到的結果如下:

Compiledfrom"StringExample.java"
publicclasscom.lagou.interview.StringExample{
publiccom.lagou.interview.StringExample();
Code:
0:aload_0
1:invokespecial#1//Methodjava/lang/Object."<init>":()V
4:return
LineNumberTable:
line3:0

publicstaticvoidmain(java.lang.String[]);
Code:
0:ldc#2//StringJava
2:astore_1
3:ldc#2//StringJava
5:astore_2
6:getstatic#3//Fieldjava/lang/System.out:Ljava/io/PrintStream;
9:aload_1
10:aload_2
11:if_acmpne18
14:iconst_1
15:goto19
18:iconst_0
19:invokevirtual#4//Methodjava/io/PrintStream.println:(Z)V
22:return
LineNumberTable:
line5:0
line6:3
line7:6
line8:22
}

從編譯程式碼 #2 可以看出,程式碼 "Ja"+"va" 被直接編譯成了 "Java" ,因此 s1==s2 的結果才是 true,這就是編譯器對字串優化的結果。

————來自拉勾教育筆記。