String的創建
String s = "hello";
JVM先根據內容"hello"查找對象,如果沒有找到,則在heap上創建新對象,并將其賦予s1,否則使用已經存在的對象
String s = new String("hello");
JVM直接在heap上創建新的對象,所以在heap中會出現內容相同,地址不同的String對象
String的比較
"==" 比較地址
"equals" 比較內容
舉例:
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
s1 == s2; // true 地址相同
s1 == s3; // false 地址不同
s1.equals(s2); // true 內容相同
s1.equals(s3); // true 內容相同
intern() 方法
查找內容相同(equals())的字符串
String s1 = "hello"; // hello不存在,jvm創建新對象 (1)
String s2 = new String("hello"); // 創舉新對象 (2),這時heap中存在兩個內容為hello的對象
s1 == s2; // false // 地址不同
s1.equals(s2); // true // 內容相同
s2 = s2.intern(); // true // 找到對象(1) 并賦予s2
s1 == s2; // true !! // 注意:此時s1,s2同指向(1)
效率:String 與 StringBuffer
情景1:
(1) String result = "hello" + " world";
(2) StringBuffer result = new String().append("hello").append(" world");
(1) 的效率好于 (2),不要奇怪,這是因為JVM會做如下處理
編譯前 String result = "hello" + " world";
編譯后 String result = "hello world";
情景2:
(1) public String getString(String s1, String s2) {
return s1 + s2;
}
(2) public String getString(String s1, String s2) {
return new StringBuffer().append(s1).append(s2);
}
(1) 的效率與 (2) 一樣,這是因為JVM會做如下處理
編譯前 return s1 + s2;
編譯后 return new StringBuffer().append(s1).append(s2);
情景3:
(1) String s = "s1";
s += "s2";
s += "s3";
(2) StringBuffer s = new StringBuffer().append("s1").append("s2").append("s3");
(2) 的效率好于(1),因為String是不可變對象,每次"+="操作都會造成構造新的String對象
情景4:
(1) StringBuffer s = new StringBuffer();
for (int i = 0; i < 50000; i ++) {
s.append("hello");
}
(2) StringBuffer s = new StringBuffer(250000);
for (int i = 0; i < 50000; i ++) {
s.append("hello");
}
(2) 的效率好于 (1),因為StringBuffer內部實現是char數組,默認初始化長度為16,每當字符串長度大于char
數組長度的時候,JVM會構造更大的新數組,并將原先的數組內容復制到新數組,(2)避免了復制數組的開銷
關鍵點
1). 簡單的認為 .append() 效率好于 "+" 是錯誤的!
2). 不要使用 new 創建 String
3). 注意 .intern() 的使用
4). 在編譯期能夠確定字符串值的情況下,使用"+"效率最高
5). 避免使用 "+=" 來構造字符串
6). 在聲明StringBuffer對象的時候,指定合適的capacity,不要使用默認值(18)
7). 注意以下二者的區別不一樣
- String s = "a" + "b";
- String s = "a";
s += "b";
String和StringBuffer之概覽
創建字符串的較佳途徑
滯留字符串帶來的優化
連接字符串時的優化技巧
借助StringBuffer的初始化過程的優化技巧
關鍵點
非可變對象一旦創建之后就不能再被改變,可變對象則可以在創建之后被改變。String對象是非可變對象,StringBuffer對象則是可變對象。為獲得更佳的性能你需要根據實際情況小心謹慎地選擇到底使用這兩者中的某一個。下面的話題會作詳細的闡述。(注意:這個章節假設讀者已經具備Java的String和StringBuffer的相關基礎知識。)
1. String s1 = "hello";
String s2 = "hello";
2. String s3 = new String("hello");
String s4 = new String("hello");
StringTest1.java
* String literals and String objects.
*/
long startTime = System.currentTimeMillis();
for(int i=0;i<50000;i++){
String s2 = "hello";
}
System.out.println("Time taken for creation of String literals : "
+ (endTime - startTime) + " milli seconds" );
long startTime1 = System.currentTimeMillis();
for(int i=0;i<50000;i++){
String s4 = new String("hello");
}
System.out.println("Time taken for creation of String objects : "
+ (endTime1 - startTime1)+" milli seconds");
}
}
這段代碼的輸出:
Time taken for creation of String literals : 0 milli seconds
Time taken for creation of String objects : 170 milli seconds
Java虛擬機會維護一個內部的滯留字符串對象的列表(唯一字符串的池)來避免在堆內存中產生重復的String對象。當JVM從class文件里加載字符串字面量并執行的時候,它會先檢查一下當前的字符串是否已經存在于滯留字符串列表,如果已經存在,那就不會再創建一個新的String對象而是將引用指向已經存在的String對象,JVM會在內部為字符串字面量作這種檢查,但并不會為通過new關鍵字創建的String對象作這種檢查。當然你可以明確地使用String.intern()方法強制JVM為通過new關鍵字創建的String對象作這樣的檢查。這樣可以強制JVM檢查內部列表而使用已有的String對象。
同一個字符串對象被重復地創建是不必要的,String.intern()方法可以避免這種情況。下圖說明了String.intern()方法是如何工作的,String.intern()方法檢查字符串對象的存在性,如果需要的字符串對象已經存在,那么它會將引用指向已經存在的字符串對象而不是重新創建一個。下圖描繪了使用了intern()方法的字符串字面量和字符串對象的創建情況。

StringTest2.java
public class StringTest2 {
String variables[] = new String[50000];
for( int i=0;i variables[i] = "s"+i;
}
long startTime0 = System.currentTimeMillis();
for(int i=0;i variables[i] = "hello";
}
System.out.println("Time taken for creation of String literals : "
+ (endTime0 - startTime0) + " milli seconds" );
long startTime1 = System.currentTimeMillis();
for(int i=0;i variables[i] = new String("hello");
}
System.out.println("Time taken for creation of String objects with 'new' key word : "
+ (endTime1 - startTime1)+" milli seconds");
long startTime2 = System.currentTimeMillis();
for(int i=0;i variables[i] = new String("hello");
variables[i] = variables[i].intern();
}
System.out.println("Time taken for creation of String objects with intern(): "
+ (endTime2 - startTime2)+" milli seconds");
}
}
Time taken for creation of String literals : 0 milli seconds
Time taken for creation of String objects with 'new' key word : 160 milli seconds
Time taken for creation of String objects with intern(): 60 milli seconds
你可以使用+操作符或者String.concat()或者StringBuffer.append()等辦法來連接多個字符串,那一種辦法具有最佳的性能呢?
請看下面的StringTest3.java代碼和輸出結果。
package com.performance.string;
public class StringTest3 {
//Test the String Concatination
long startTime = System.currentTimeMillis();
for(int i=0;i<5000;i++){
String result = "This is"+ "testing the"+ "difference"+ "between"+
"String"+ "and"+ "StringBuffer";
}
System.out.println("Time taken for string concatenation using + operator : "
+ (endTime - startTime)+ " milli seconds");
long startTime1 = System.currentTimeMillis();
for(int i=0;i<5000;i++){
StringBuffer result = new StringBuffer();
result.append("This is");
result.append("testing the");
result.append("difference");
result.append("between");
result.append("String");
result.append("and");
result.append("StringBuffer");
}
System.out.println("Time taken for String concatenation using StringBuffer : "
+ (endTime1 - startTime1)+ " milli seconds");
}
}
這是上面的代碼的輸出結果:
Time taken for String concatenation using + operator : 0 milli seconds
Time taken for String concatenation using StringBuffer : 50 milli seconds
很有趣地,+操作符居然比StringBuffer.append()方法要快,為什么呢?
String result = "This is"+"testing the"+"difference"+"between"+"String"+"and"+"StringBuffer";
編譯后:
String result = "This is testing the difference between String and StringBuffer";
這里String對象在編譯期就決定了而StringBuffer對象是在運行期決定的。運行期決定需要額外的開銷當字符串的值無法預先知道的時候,編譯期決定作用于字符串的值可以預先知道的時候,下面是一個例子。
public String getString(String str1,String str2) {
return str1+str2;
}
編譯后:
return new StringBuffer().append(str1).append(str2).toString();
運行期決定需要更多的時間來運行。
看看下面的代碼你會發現與情景一相反的結果――連接多個字符串的時候StringBuffer要比String快。
StringTest4.java
using + operator and StringBuffer */
public class StringTest4 {
//Test the String Concatenation using + operator
long startTime = System.currentTimeMillis();
String result = "hello";
for(int i=0;i<1500;i++){
result += "hello";
}
System.out.println("Time taken for string concatenation using + operator : "
+ (endTime - startTime)+ " milli seconds");
long startTime1 = System.currentTimeMillis();
StringBuffer result1 = new StringBuffer("hello");
for(int i=0;i<1500;i++){
result1.append("hello");
}
System.out.println("Time taken for string concatenation using StringBuffer : "
+ (endTime1 - startTime1)+ " milli seconds");
}
}
這是上面的代碼的輸出結果:
Time taken for string concatenation using + operator : 280 milli seconds
Time taken for String concatenation using StringBuffer : 0 milli seconds
原因是兩者都是在運行期決定字符串對象,但是+操作符使用不同于StringBuffer.append()的規則通過String和StringBuffer來完成字符串連接操作。(譯注:什么樣的規則呢?)
你可以通過StringBuffer的構造函數來設定它的初始化容量,這樣可以明顯地提升性能。這里提到的構造函數是StringBuffer(int length),length參數表示當前的StringBuffer能保持的字符數量。你也可以使用ensureCapacity(int minimumcapacity)方法在StringBuffer對象創建之后設置它的容量。首先我們看看StringBuffer的缺省行為,然后再找出一條更好的提升性能的途徑。
StringBuffer在內部維護一個字符數組,當你使用缺省的構造函數來創建StringBuffer對象的時候,因為沒有設置初始化字符長度,StringBuffer的容量被初始化為16個字符,也就是說缺省容量就是16個字符。當StringBuffer達到最大容量的時候,它會將自身容量增加到當前的2倍再加2,也就是(2*舊值+2)。
Time taken for String concatenation using StringBuffer with out setting size: 280 milli seconds
Time taken for String concatenation using StringBuffer with setting size: 0 milli seconds
1. 無論何時只要可能的話使用字符串字面量來常見字符串而不是使用new關鍵字來創建字符串。
2. 無論何時當你要使用new關鍵字來創建很多內容重復的字符串的話,請使用String.intern()方法。
3. +操作符會為字符串連接提供最佳的性能――當字符串是在編譯期決定的時候。
4. 如果字符串在運行期決定,使用一個合適的初期容量值初始化的StringBuffer會為字符串連接提供最佳的性能。
String類提供了一些方法,用來進行字符串的比較。這個類實現了Object父類的equals()方法,用來比較兩種字符串的值是否相等。同時還增加了equalsIgnoreCase()方法可以忽略兩個字符串大小寫的區別。下面是這兩種方法的例子。
【例6-6】
public class E6_6{
public static void main(String args[]) {
String s1="a";
String s2=new String("a");
String s3="A";
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println(s1.equalsIgnoreCase(s3));
}
}
上例的輸出是
true
flase
true
但是StringBuffer類并沒有實現Objcet類的Equals方法,所以不能用這個方法來比較兩個StringBuffer類的字符串是否相等,如下例所示。
【例6-7】
public class E6_7{
public static void main(String args[]) {
StringBuffer s1=new StringBuffer("a");
StringBuffer s2=new StringBuffer("a");
System.out.println(s1.equals(s2));
}
}
程序輸出:false
除了用equalse方法來比較兩個字符串外,還可以用==來比較字符串。與equalse方法不同的是,==不是比較兩個字符串的值是否相等,而是比較幾個字符串的引用是否指向同一個實例。如例6-8所示。
【例6-8】
public class E6_8{
public static void main(String args[]) {
String s1="a";
String s2="a";
String s3=new String("a");
String s4=new String("a");
System.out.println(s1==s2);
System.out.println(s3==s4);
System.out.println(s1==s3);
}
}
上面的程序段輸出:
true
false
false
與上例進行比較,不僅可以看出來==與equals的區別,還可以看到字面量的String的特殊之外。
對于字面量的String,只要字符串的值是相等的,不論有多少個引用都是指向同一塊內存,不再另外分配空間。而用new關鍵字生成的實例則不同,每當用new實例化一次,分配該實例自己的內存空間。上例的存儲方式如圖所示:
圖6-1 s1、s2、s3、s4的區別
下面再通過另一個例子來看String和StringBuffer的區別。
【例6-9】
public class E6_9{
public static void main(String args[]) {
String s1="a";
StringBuffer sb1=new StringBuffer("a");
StringBuffer sb2=sb1;
String s2="a"+"b";
sb1.append("b");
System.out.println(s1==s2);
System.out.println(sb1==sb2);
}
}
上例輸出的是:
flase
true
上例可以證明這樣的結論:String是不可變長的字符串,對String做任何的修改將生成新的字符串,而StringBuffer是可變長的字符串,不論怎么更動還是同一個字符串。