編寫高質量程式碼:改善Java程式的151個建議(第4章:字串___建議52~59)
生活不只眼前的苟且。還有讀不懂的詩和到不了的遠方。 --閆妮
建議52:推薦使用String直接賦值
建議53:注意方法中傳遞的引數要求
建議54:正確使用String、StringBuffer、StringBuilder
建議55:注意字串的位置
建議56:自由選擇字串的拼接方法
建議57:推薦在複雜字串操作中使用正則表示式
建議58:強烈建議使用UTF編碼
建議59:對字串持有一種寬容的心態
建議52:推薦使用String直接賦值
建議53:注意方法中傳遞的引數要求
建議54:正確使用String、StringBuffer、StringBuilder
1、Java String類
字串廣泛應用在Java程式設計中,在Java中字串屬於物件,Java提供了String類來建立和操作字串。
需要注意的是String的值是不可變的,這就導致每次對String的操作都會生成新的String物件,這樣不僅效率低下,而且浪費有限的記憶體空間。
我們可以看到,初始String值為“hello”,然後在這個字串後面加上新的字串“world”,這個過程是需要重新在棧堆記憶體中開闢記憶體空間的,最終得到了“hello world”字串也相應的需要開闢記憶體空間,這樣短短的兩個字串,卻需要開闢三次記憶體空間,不得不說這是對記憶體空間的極大浪費。為了應對經常性的字串相關的操作,Java引入了兩個新的類——StringBuffer類和StringBuild類來對此種變化字串進行處理。
2、StringBuffer 和 StringBuilder 類
三者區別:
string:不可變字元序列
StringBuffer:可變字元序列、執行緒安全、效率低
StringBuilder:可變字元序列、執行緒不安全、效率高
注:
String的使用陷阱:
String ss = "a";
ss +="b";
如果多次執行這些改變字串內容的操作,會導致大量副本字串物件存在記憶體中,降低效率。如果這樣的操作放在迴圈中,會極大影響程式的效能。
3、效能測試
String的拼接與StringBuilder的使用對比,簡單的12個字串的迴圈解析拼接,相差了28毫秒,沒有對比就沒有傷害,以後用StringBuilder吧,暫時還不知道這個東西會有什麼弊端,持續觀察吧
建議55:注意字串的位置
public class Client55 {
public static void main(String[] args) {
String str1 = 1 + 2 + "apples";
String str2 = "apples" + 1 + 2;
System.out.println(str1);
System.out.println(str2);
}
}
答案是不一致,str1的值是"3apples" ,str2的值是“apples12”。
建議56:自由選擇字串的拼接方法
package OSChina.Client;
public class Client1 {
public static void main(String[] args) {
// 加號拼接
String str = "";
long start1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
str += "江疏影";
}
long end1 = System.currentTimeMillis();
System.out.println("加號拼接耗時:" + (end1 - start1) + "ms");
// concat拼接
str = "";
long start2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
str = str.concat("江疏影");
}
long end2 = System.currentTimeMillis();
System.out.println("concat拼接耗時:" + (end2 - start2) + "ms");
// StringBuilder拼接
str = "";
StringBuilder buffer = new StringBuilder("");
long start3 = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
buffer.append("江疏影");
}
long end3 = System.currentTimeMillis();
System.out.println("StringBuilder拼接耗時:" + (end3 - start3) + "ms");
// StringBuffer拼接
str = "";
StringBuffer sb = new StringBuffer("");
long start4 = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
sb.append("江疏影");
}
long end4 = System.currentTimeMillis();
System.out.println("StringBuffer拼接耗時:" + (end4 - start4) + "ms");
}
}
從上面的執行結果來看,在字串拼接方式中,StringBuilder的append方法最快,StringBuffer的append方法次之(因為StringBuffer的append方法是執行緒安全的,同步方法自然慢一點),其次是concat方法,加號最慢,這是為何呢?
速度:StringBuilder>StringBuffer>concat>String+
原理:
1、“+”方法拼接字串:
str= new StringBuilder(str).append("c").toString();
它與純粹使用StringBuilder的append方法是不同的:一是每次迴圈都會建立一個StringBuilder物件,二是每次執行完畢都要呼叫toString方法將其轉換為字串——它的執行時間就耗費在這裡了!
2、concat方法拼接字串:我們從原始碼上看一下concat方法的實現,程式碼如下:
public String concat(String str) {
int otherLen = str.length();
//如果追加字元長度為0,則返回字串本身
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
//產生一個新的字串
return new String(buf, true);
}
其整體看上去就是一個數組拷貝,雖然在記憶體中處理都是原子性操作,速度非常快,不過,注意看最後的return語句,每次concat操作都會建立一個String物件,這就是concat速度慢下來的真正原因,它建立了10萬個String物件呀。
3、append方法拼接字串:StringBuilder的append方法直接由父類AbstractStringBuilder實現,其程式碼如下:
public AbstractStringBuilder append(String str) {
//如果是null值,則把null作為字串處理
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
//字串複製到目標陣列
str.getChars(0, len, value, count);
count += len;
return this;
}
整個append方法都在做字元陣列處理,加長,然後拷貝陣列,這些都是基本的資料處理,沒有建立任何物件,所以速度也就最快。
4、StringBuffer的處理和此類似,只是方法是同步的而已。
建議57:推薦在複雜字串操作中使用正則表示式
建議58:強烈建議使用UTF編碼
注:一個系統使用統一的編碼。
建議59:對字串持有一種寬容的心態
中文的排序問題很混亂,Java使用UNICODE編碼,而中文UNICODE字符集來源於GB2312,GB2312是一個包含了7000多個字元的字符集,它是按照拼音排序,並且是連續的,之後的GBK、GB18030都是在其基礎上擴充而來的,所以要讓它們完整的排序也就難上加難了。
如果排序物件是經常使用的漢字,使用Collator類排序完全可以滿足我們的要求,畢竟GB2312已經包含了大部分的漢字,如果需要嚴格排序,則要使用一些開源專案來自己實現了。
注意:如果排序不是一個關鍵演算法,使用Collator類即可。