Java 字元串比較、拼接問題
@
/——————————————————字元串類型—————————————————/
Java中用於處理字元串常用的有三個類:
1、java.lang.String
2、java.lang.StringBuffer
3、java.lang.StrungBuilder
相同點: 都是final類, 不允許被繼承;
不同點:
- StringBuffered/StringBuilder 都繼承自抽象類AbstractStringBuilder
(實現了Appendable, CharSequence介面),可以通過append()、indert()進行字元串的操作 - String實現了三個介面: Serializable、Comparable
、CarSequence,
String的實例可以通過compareTo方法進行比較
StringBuilder/StringBuffer只實現了兩個介面Serializable、CharSequence - StringBuffer是執行緒安全的(Synchronized 加鎖),可以不需要額外的同步用於多執行緒中
StringBuilder不是執行緒安全的,但是效率比StringBuffer高
/—————————————本篇主要討論String類型————————————/
1.字元串的比較
1. 1 字元串常量池
字元串常量池(以下簡稱常量池/字元串池)的存在意義:實際開發中,String類是使用頻率非常高的一種引用對象類型。但是不斷地創建新的字元串對象,會極大地消耗記憶體。因此,JVM為了提升性能和減少記憶體開銷,內置了一塊特殊的記憶體空間即常量池,以此來避免字元串的重複創建。JDK 1.8 後,常量池被放入到堆空間中。
字元串池中維護了共享的字元串對象,這些字元串不會被垃圾收集器回收。
1.2 String類型的比較方式
若直接使用「==」進行比較對象,則比較的是兩個對象的引用地址;
若使用str1.equals(str2)方法進行比較,由於String類內部已經覆蓋Object類中的equals()方法,實際比較的是兩個字元串的值。
- 比較原理:
先判斷對象地址是否相等,若相等則直接返回true;
若不相等再去參數判斷括弧內傳入的參數是否為String類型的:若不是字元串將最終返回false;若是字元串,再依次比較所有字元是否一樣。
// 源碼
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value) : StringUTF16.equals(value, aString.value);
}
}
return false;
}
1.3 String的創建方式
1.3.1 直接使用「=」進行賦值
String str_01 = "aa";
String str_02 = "aa";
System.out.println(str_01 == str_02);
使用這種方式創建字元串,會先在棧中創建一個引用變數str_01,再去常量池中尋找是否已存在值為”aa”的字元串:
- 如果不存在這樣的字元串,則會在常量池中新建一個”aa”字元串對象,並把這個字元串對象的引用地址賦值給對象str_01;
- 如果常量池中尋找已存在這樣的字元串,則不會再創建新的對象,直接返回已存在的對象地址,並將其賦值給對象str_02;
// result
true
1.3.2 使用「new」關鍵字創建新對象
String str_01 = new String("xyz");
String str_02 = new String("xyz");
System.out.println(str_01 == str_02);
這種方式至少會創建一個對象,因為本質是調用了String類的構造器方法public String(String original){…},在堆中一定會創建一個字元串對象。
使用”new”關鍵字創造對象主要分為三步:
- 在堆中會創建一個字元串對象;
- 判斷常量池是否存在與構造器參數中的字元串值相等的常量;
- 如果常量池中已有這樣的字元串存在,則直接返回堆中的字元串對象引用地址,賦值給棧中的變數;如果不存在,會先創建一個字元串對象在常量池中,然後返回堆中的對象引用地址,賦值給棧中的變數。
// result
false
1.3.3 intern()方法返回的引用地址
String str_01 = new String("abc").intern();
String str_02 = "abc";
String str_03 = new String("abc");
System.out.println(str_01 == str_02);
System.out.println(str_02 == str_03);
String str_04 = new String("cba");
String str_05 = new String("cba").intern();
System.out.println(str_04 == str_05);
當使用構造器創建字元串調用 intern()方法時,如果常量池中已經存在一個值相同的字元串(內部使用equals()方法來確定),則返回常量池中的字元串對象的引用地址;否則,將堆中新創建的字元串對象添加到常量池中,並返回池中字元串對象的引用地址。
// result
true
false
false
2. 字元串類的可變性與不可變性
字元串的本質:char類型數組 private final char[] str
String類實現了CharSequence介面
String類型的不可變性指的是記憶體地址不可變,如果將一個對象重新賦值,則本質上是改變了其引用對象。
String a = "hello";
System.out.println(a.hashCode());
a = "hey";
System.out.println(a.hashCode());
// result
99162322
103196
StringBuffer類型和StringBuilder類型的字元串定義好後可以進行值改變,並且不會創建新的記憶體地址。
StringBuilder a = new StringBuilder();
System.out.println(a.hashCode());
a.append("Hello");
a.append("World");
System.out.println(a.hashCode());
// result
1395089624
1395089624
3. 字元串的相加/拼接
3.1 字元串與非字元串類型的相加/拼接
String類中的valueOf(Object obj)方法可以將任意一個對象轉換為字元串類型。
// 源碼
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
String類中,重載了+與+=運算,這也是Java中唯一重載的兩個運算符。
兩個字元串相加即是字元串的拼接,在進行拼接時,會先調用valueOf(Object obj)方法將其為字元串類型,再進行拼接。從源碼可以看出,如果字元串為null,會將其轉換為字面值為”null”的字元串。
String s = null;
s = s + "World";
System.out.println("Hello " +s);
// result: Hello nullWorld
因此在進行字元串拼接時,初始字元串應該設置成空字元串””,而非null。
3.2 兩個String類型對象相加/拼接原理
在字元串間使用加法運算時:
- 若是常量字元串相加,如: “AB”+”CD”,則是編譯優化。
凡是單獨使用雙引號” “引用起來的內容直接拼接時,均會被編譯優化,編譯時就已經確定其值,即為拼接後的值。 - 若是字元串變數相加,如:
String temp1 = “AB”;
String temp2 = “CD”;
String str = temp1 + temp2;
則是在底層調用了StringBuilder類中的構造方法及append()方法來輔助完成:
String str = new StringBuilder().append(temp1).append(temp2).toString();
String str1 = "ABCD";
String str2 = "AB" + "CD";
String str3 = "A" + "B" + "C" + "D";
String temp1 = "AB";
String temp2 = "CD";
String str4 = temp1 + temp2;
// String str4 = new StringBuilder().append(temp1).append(temp2).toString();
String temp = "AB";
String str5 = temp + "CD";
// String str4 = new StringBuilder(String.valueOf(temp)).append("CD").toString();
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str1 == str4);
System.out.println(str1 == str5);
// result
true
true
false
false
4. final類型的String類字元串
public class test {
public static final String str1 = "abc";
public static final String str2 = "def";
public static void main(String[] args) {
String str3 = str1 + str2;
String str4 = "abcdef";
System.out.println(str3 == str4);
}
}
str1和str2都是final類型的,並且在編譯階段都是已經被賦值了,相當於一個常量,當執行Strings str3 = str1 + str2 的時候,str3已經是”abcdef”常量了,已被創建在常量池中,所以地址是相等的。
// result
true
public class test {
public static final String s1;
public static final String s2;
static{
s1 = "ab";
s2 = "cd";
}
public static void main(String[] args) {
String s3 = s1 + s2;
String s4 = "abcd";
System.out.println(s3 == s4);
}
}
雖然s1和s2都是final類型,但是起初並沒有初始化,在編譯期還不能確定具體的值,此處是變數,所以這裡會調用StringBuilder類中的構造方法及append()方法來創建新的字元串s3,返回的新字元串s3在堆中的地址,所以與s4不相等。
// result
false
參考內容:
- java-String常量池的知識點你知道多少?-結合jdk版本變更 by hz90s
- java中String、StringBuffer和StringBuilder的區別(簡單介紹) by 韋邦杠
- Java String:字元串常量池(轉)by 暖暖-木木
If you have any question, please let me know, your words are always welcome.*
新人入坑,如有錯誤/不妥之處,歡迎指出,共同學習。