Java中String,StringBuffer,StringBuilder基礎知識

真丶深紅騎士發表於2019-02-16

前言

在平時開發中,我們很多時候都會用到StringStringBufferStringBuilde這三者。那麼這三者究竟是什麼呢,下面一一講述。

String

String
檢視api文件,可以知道,String是繼承object類,並實現了序列化介面,字元序列介面,字串排序介面。String是Java中的字串,Java程式中的所有字串字面值(如"abc")都作為此類的例項實現,字串是常量,它們的值在建立之後不能更改,字串緩衝區支援可變的字串。因為String物件是不可變的,所有可以共享,例如:String str1 = "abc";等效於char data[] = {'a','b','c'};String str2 = new String(data);,因為String是不可變的物件,每次對String型別改變的時候其實都等同於生成一個新的String物件,然後將指標指向新的String物件,所以經常改變內容的字串最好不要用String,因為每次生成物件都會對系統效能產生影響,當記憶體多了無引用的物件,JVM的GC就會工作,頻繁GC就會造成介面的卡頓,下面直接上例子:

直接賦值和建立物件

情況一

		 String str1 = "abc";
		 char data[] = {'a','b','c'};
		 String str2 = new String(data);
		 //輸出true
		 System.out.println(str1.equals(str2));
		 //輸出false
		 System.out.println(str1 == str2);
		 
		 String str3 = "This is Java";
		 String str4 = new String("This is Java");
		 //輸出true
		 System.out.println(str3.equals(str4));
		 //輸出false
		 System.out.println(str3 == str4); 
		  
複製程式碼

上面程式碼str1str3是直接賦值的,str2是通過字元陣列來構建一個字串常量,str4是直接通過new關鍵字來建立物件。可以看到用equals比較str1str2str3str4是相等的,用==比較str1str2str3str4是不相等的。這裡簡單說一下equals==的簡單區別:

  • ==操作符的作用
  1. 比較基本資料型別是否相同
  2. 判斷引用是否指向堆記憶體的同一塊地址,也就是是否指向同一個物件
  • equasl方法的作用
  1. 判斷兩個變數是否對同一個物件的引用,也就是記憶體堆中的內容是否相同,其實就是對String物件所封裝的字串內容作比較,具體的值是否相等,如果兩個String物件所封裝的字元型內容相同,則equals()方法將返回true

而上面的情況因為str1str2是兩個不同的物件,但是內容相等,所以==返回了false,而equals返回true,str3str4也是一樣道理,從上面可以簡單得出一個結論,無論上直接賦值還是通過new關鍵字來建立String物件,都會生成兩個不同的String物件,即使字元型內容相同。

情況二

		 String str5 = "This is Java";
		 String str6 = "This is";
		 String str7 = "This is" + " Java";
		 //輸出true
		 System.out.println(str5.equals(str7));
		 //輸出true
		 System.out.println(str5 == str7); 
複製程式碼

從上面程式碼可以看出,無論是用==或者equals來比較兩個沒有引用並且內容值是相等返回都是true,也就是String str7 = "This is" + " Java";這句話沒有建立新的物件,而是引用指向str5的地址。

情況三

		 String str8 = "This is Java";
		 String str9 = "This is";
		 String str10 = str9 + " Java";
		 //輸出true
		 System.out.println(str8.equals(str10));
		 //輸出false
		 System.out.println(str8 == str10); 
複製程式碼

上面發現用==比較時,str8str10不是指向同一個地址,這是因為在編譯時str8str9就已經確定了,而str10是引用變數,不會再編譯時確定,所以會建立String物件。

情況四

		 String str11 = "This is Java";
		 String str12 = new String("This is Java");
		 String str13 = str12;
		 
		 //輸出true
		 System.out.println(str11.equals(str12));
		 //輸出true
		 System.out.println(str11.equals(str13));
		 //輸出true
		 System.out.println(str12.equals(str13));
		 //輸出false
		 System.out.println(str11 == str13); 
		 //輸出false
		 System.out.println(str11 == str12); 
		 //輸出true
		 System.out.println(str12 == str13); 
複製程式碼

因為三個String物件的值都是相等的,所以用equals來比較是返回true,因為str13是指向str12,相當於傳遞引用,所以用==來比較是相等的,都是同一個物件,用一張圖來比較直觀:

String圖一
這裡簡單說一下,因為一開始String str11 = "This is Java";在常量池已經建立This is Java這個常量,所以String str12 = new String("This is Java");這句程式碼不會再常量池建立物件,只會在堆上建立物件This is Java,也就是說new String建立字串它其實分兩步操作:

  1. 在堆上建立物件
  2. 檢查常量池有沒有字串字面量,如果沒有就在常量池建立常量,如果存在就不做任何操作

情況五

		 
		 String str14 = "This is Java";
		 String str15 = "This is Java";
		 String str16 = "This is Java";
		 
		 //輸出true
		 System.out.println(str14.equals(str15));
		 //輸出true
		 System.out.println(str14.equals(str16));
		 //輸出true
		 System.out.println(str15.equals(str16));
		 //輸出true
		 System.out.println(str14 == str15); 
		 //輸出true
		 System.out.println(str14 == str16); 
		 //輸出true
		 System.out.println(str15 == str16); 
複製程式碼

直接上圖:

String 圖2

情況六

		 String str17 = new String("This is Java");
		 String str9 = str17.intern();
		 String str18 = "This is Java";
		 //輸出false
		 System.out.println(str9 == str17);
		 //輸出true
		 System.out.println(str9 == str18);
複製程式碼

上面呼叫了intern這個方法,這句話就是把字串物件加入常量池中,實際操作如下,直接上圖吧:

intern
所以上面程式碼,用下面圖顯示:

String intern示意圖

總結

  1. String a = "xxx" 可能建立一個或者不建立物件,當xxx這個字元常量在String常量池不存在,會在String常量池建立一個String物件,然後a會指向這個記憶體地址,後面繼續用這種方式繼續建立xxx字串常量,始終只有一個記憶體地址被分配。
  2. String a = new String("xxx")至少建立一個物件,也有可能兩個。只要用到new就肯定在堆上建立一個String物件,並且檢查String常量池是否存在,如果不存在就會在Stirng常量池建立這個String物件,存在就不建立。

StringBuffer字串變數

StringBuffer
api文件上,同樣可以知道StringBuffer繼承Object類,並實現了序列化介面,能夠被新增char序列和值的物件介面,可讀序列介面。和String相比,沒有實現Comparable,而是實現了Appendable。它是執行緒安全的可變字元序列,一個類似於String的字串緩衝區,但是不能修改,雖然在任意時間點上它都包含某種特定的字元序列,但通過某些方法呼叫的可以改變該序列的長度和內容。StringBuffer上的主要操作是appendinsert方法,可以過載這些方法,以接受任意型別的資料,每個方法都能有效地將給定的資料轉換成字串,然後將該字串的字元追加或者插入到字串緩衝區中。append方法始終將這些字元新增到緩衝區的末端,而insert方法則在指定的點新增字元。StringBufferString不一樣,它是**字串變數,是可以改變的物件,當對字串做操作時,就是在物件上做操作,不會像String一樣建立額外的物件進行操作。

 StringBuffer str20 = new StringBuffer("This is");
		 //StringBuffer後面追加內容
		 str20.append(" Java");
		 System.out.println(str20); //輸出:This is Java
		 //刪除第6-8個字元
		 str20.delete(5, 8);
		 System.out.println(str20); //輸出:This Java
		 //在9個位置插入
		 str20.insert(9," is good"); 
		 System.out.println(str20); //輸出:This Java is good
		 //替換5-9
		 str20.replace(5, 9, "C++");
		 System.out.println(str20); //輸出:This C++ is good
		 //倒序
		 str20.reverse();
		 System.out.println(str20); //輸出:doog si ++C sihT
		 
		 //變成大寫
		 System.out.println(str20.toString().toUpperCase()); //輸出:DOOG SI ++C SIHT
		 
		 //第一種方案StringBuffer轉化成String
		 String str21 = new String(str20);
		 System.out.println(str21); //輸出:doog si ++C sihT
		 
		 //第二種方案
		 String str22 = str20.toString();
		 System.out.println(str22); //輸出:doog si ++C sihT
複製程式碼

StringBuilder字串變數

StringBuilder示意圖
StringBuffer一樣,也是實現SerializableAppendableCharSequence介面。一個可變的字元序列,這個類提供一個與StringBuffer相容的API,但是不保證同步,該類被設計用作StringBuffer的一個簡易替換,用在字串緩衝區被單個執行緒使用的時候。如果可能,建議優先採用該類,因為在大多數實現中,比StringBuffer要快。在StringBuilder 上的主要操作是appendinsert方法,可過載這些方法,以接受任意型別的資料。每個方法都能有效地將給定的資料轉換成字串,然後將該字串的字元追加或插入到字串生成器中。append方法始終將這些字元新增到生成器的末端;而 insert方法則在指定的點新增字元。將StringBuilder的例項用於多個執行緒是不安全的。如果需要這樣的同步,則建議使用StringBuffer。主要一些操作:

	     StringBuilder str23 = new StringBuilder("This is"); 
		 //StringBuilder後面追加內容
		 str23.append(" Java");
		 System.out.println(str23); //輸出:This is Java
		 //刪除第6-8個字元
		 str23.delete(5, 8);
		 System.out.println(str23); //輸出:This Java
		 //在9個位置插入
		 str23.insert(9," is good"); 
		 System.out.println(str23); //輸出:This Java is good
		 //替換5-9
		 str23.replace(5, 9, "C++");
		 System.out.println(str23); //輸出:This C++ is good
		 //倒序
		 str23.reverse();
		 System.out.println(str23); //輸出:doog si ++C sihT
		 
		 //變成大寫
		 System.out.println(str23.toString().toUpperCase()); //輸出:DOOG SI ++C SIHT
		 
		 //第一種方案StringBuffer轉化成String
		 String str24 = new String(str23);
		 System.out.println(str24); //輸出:doog si ++C sihT
		 
		 //第二種方案
		 String str25 = str23.toString();
		 System.out.println(str25); //輸出:doog si ++C sihT
		 
複製程式碼

三者速度比較

        //做賦值操作 
		//String賦值
		String str = new String("I Love Android");
		long starttime1 = System.currentTimeMillis();
		for (int i = 0; i < 100000; i++) {
			str = str + "!";
		}
		long endtime1 = System.currentTimeMillis();
		System.out.println("String花費的時間 :" + (endtime1 - starttime1));
		
		//StringBuilder	
		StringBuilder str3 = new StringBuilder("I Love C++");
		long starttime3 = System.currentTimeMillis();
		for (int i = 0; i < 100000; i++) {
			str3 = str3.append("!");
		}
		long endtime3 = System.currentTimeMillis();
		System.out.println("StringBuilder花費的時間 :" + (endtime3 - starttime3));
					
		//StringBuffer
		StringBuffer str2 = new StringBuffer("I Love Java");
		long starttime2 = System.currentTimeMillis();
		for (int i = 0; i < 100000; i++) {
			str2 = str2.append("!");
		}
		long endtime2 = System.currentTimeMillis();
		System.out.println("StringBuffer花費的時間 :" + (endtime2 - starttime2));
		
			
複製程式碼

執行截圖:

三者執行時間

總結

  • String :不可變類,任何對String的改變都會引發新的String物件的生成,適用於少量的字串操作的情況
  • StringBuffer :執行緒安全,任何對它所指代的字串的改變都不會產生新的物件,適用多執行緒下在字元緩衝區進行大量操作的情況
  • StringBuilder :執行緒不安全,因此不適合多執行緒中使用,適用於單執行緒下在字元緩衝區進行大量操作的情況
  • 速度執行方面:StringBuilder > StringBuffer > String

相關文章