Java 字符串比較、拼接問題

@html


/—————————————————— 字符串類型—————————————————/
Java中用於處理字符串經常使用的有三個類:

一、java.lang.Stringjava

二、java.lang.StringBuffer數組

三、java.lang.StrungBuilder安全

相同點: 都是final類, 不容許被繼承;多線程

不一樣點:app

  • 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類型的比較方式

若直接使用「==」進行比較對象,則比較的是兩個對象的引用地址;ui

若使用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"的字符串:

  1. 若是不存在這樣的字符串,則會在常量池中新建一個"aa"字符串對象,並把這個字符串對象的引用地址賦值給對象str_01;
  2. 若是常量池中尋找已存在這樣的字符串,則不會再建立新的對象,直接返回已存在的對象地址,並將其賦值給對象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"關鍵字創造對象主要分爲三步:

  1. 在堆中會建立一個字符串對象;
  2. 判斷常量池是否存在與構造器參數中的字符串值相等的常量;
  3. 若是常量池中已有這樣的字符串存在,則直接返回堆中的字符串對象引用地址,賦值給棧中的變量;若是不存在,會先建立一個字符串對象在常量池中,而後返回堆中的對象引用地址,賦值給棧中的變量。
// 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

參考內容:

  1. java-String常量池的知識點你知道多少?-結合jdk版本變動 by hz90s
  2. java中String、StringBuffer和StringBuilder的區別(簡單介紹) by 韋邦槓
  3. Java String:字符串常量池(轉)by 暖暖-木木

If you have any question, please let me know, your words are always welcome.* 新人入坑,若有錯誤/不妥之處,歡迎指出,共同窗習。

相關文章
相關標籤/搜索