字串是軟體開發中最重要的對象之一。通常,字串對象在記憶體中是佔據了最大的空間塊,因此如何高效地處理字串,必將是提高整體效能的關鍵所在。
1.字串對象及其特點
Java中八大基礎資料型別 (Elementary Data Type)沒有String類型,因為String類型是Java對char數組的進一步封裝。
String類的實現主要由三部分組成:char數組,offset位移量,String的長度。
String類型有三個基本特點:
不變性
不變性是指String對象一旦產生,則不能再對它進行改變。
不變性的作用在於當一個對象需要被多線程共用,並且頻繁訪問時,可以省略同步和鎖等待的時間,從而大幅提高系統效能。
針對常量池的最佳化
當兩個String對象擁有相同的值時,它們只引用常量池中的同一個拷貝。
類的final定義
作為final類的String對象在系統中不能有任何子類,這是對系統安全性的保護!
1.1 subString()方法的記憶體流失
關於這一點,在JDK的1.7及以後就已經解決了!
在1.7之前,subString()方法截取字串只是移動了位移量,截取之後的字串實際上還是原來的大小。
現在,當使用subString()方法截取字串時會把截取後的字串拷貝到新對象。
1.2 字串分割與尋找1、原始的String.split()
String.split()方法使用簡單,功能強大,支援Regex,但是,在效能敏感的系統中頻繁的使用這個方法是不可取的。
注意 * ^ : | . 這些符號記得\\轉義
2、使用效率更高的StringTokenizer類分割字串
StringTokenizer類是JDK中提供的專門用來處理字串分割的工具類。構造方法:
public StringTokenizer(String str, String delim, boolean returnDelims)
其中str是要分割的字串,delim是分割符,returnDelims是否返回分隔字元,預設false。
String s = "a;b;c"; StringTokenizer stringTokenizer = new StringTokenizer(s, ";", false); System.out.println(stringTokenizer.countTokens()); while (stringTokenizer.hasMoreTokens()) { System.out.println(stringTokenizer.nextToken()); }
3、最佳化的字串分割方式
indexOf()方法是一個執行速度非常快的方法,subString()是採用了時間換空間技術,因此速度相對快。
public static List<String> mySplit(String str, String delim){ List<String> stringList = new ArrayList<>(); while(true) { int k = str.indexOf(delim); if (k < 0){ stringList.add(str); break; } String s = str.substring(0, k); stringList.add(s); str = str.substring(k+1); } return stringList; }
4、三種分割方法的對比與選擇
split()方法功能強大,但是效率最差;
StringTokenizer效能優於split方法,能用StringTokenizer就沒必要用split();
自己實現的分割演算法效能最好,但代碼的可讀性和系統的可維護性最差,只有當系統效能成為主要矛盾時,才推薦使用該方法。
5、高效率的charAt方法
charAt(int index) 返回指定索引處的 char 值。功能和indexOf()相反,效率卻一樣高。
6、字串前後輟判斷
public boolean startsWith(String prefix)
測試此字串是否以指定的首碼開始
public boolean endsWith(String suffix)
測試此字串是否以指定的尾碼結束
這兩個Java內建函數效率遠遠低於charAt()方法。單元測試:
@Test public void test(){ String str = "hello"; if (str.charAt(0)=='h'&&str.charAt(1)=='e'){ System.out.println(true); } if (str.startsWith("he")){ System.out.println(true); } }
1.3 StringBuffer和StringBuilder1、String常量的累加操作
String s = "123"+"456"+"789";
雖然從理論上說字串的累加的效率並不高,但該語句執行耗時為0;反編譯代碼後,我們發現代碼是
String s = "123456789";
顯然,是Java在編譯時間做了充分的最佳化。因此,並沒有想象中那樣產生大量的String執行個體。
對於靜態字串的串連操作,Java在編譯時間會進行徹底的最佳化,將多個串連操作的字串在編譯時間合成一個單獨的長字串。
2、String變數累加的操作
String str = "hello"; str+="word"; str+="!!!";
我們利用“+=”改變字串內容的值,實際上字串根本沒有改變。
當 str+="word"
時,堆記憶體開闢了word
的記憶體空間和helloword
的兩個記憶體空間(相當於執行個體化了兩個String對象),並把str的引用指向了helloword
,原來的hello
和word
成為了垃圾被JVM回收。
3、concat() 連接字串
String的concat()是專門用於字串串連操作的方法,效率遠遠高於“+”或者“+=”。
4、StringBuffer和StringBuilder
不用多說了,就是為字串串連而生的,效率最高。不同的是,StringBuffer幾乎對所有的方法都做了同步,StringBuilder並沒有做任何同步,效率更高一些。只不過在多線程系統中,StringBuilder無法保證安全執行緒,不能使用。
5、容量參數
StringBuffer和StringBuilder的是對String的封裝,String是對char數組的封裝。是數組就有大小,就有不夠用的時候,不夠用只能擴容,也就是把原來的再複製到新的數組中。合適的容量參數自然能夠減少擴容的次數,達到提高效率的目的。
在初始化時,容量參數預設是16個位元組。在構造方法中指定容量參數:
public StringBuilder(int capacity)
1.4 附一些實用的方法
判斷字串相等(忽略大小寫)
equalsIgnoreCase(String anotherString)
判斷是否存在子字串(返回布爾類型)
contains(CharSequence s)
將指定字串串連到此字串的結尾
concat(String str)
使用指定的格式字串和參數返回一個格式化字串
format(String format, Object... args)
使用預設語言環境的規則將此 String 中的所有字元都轉換為小寫。
toLowerCase()
使用預設語言環境的規則將此 String 中的所有字元都轉換為大寫。
toUpperCase()
返回字串的副本,忽略前置空白和尾部空白。
trim()
使用給定的 replacement 替換此字串所有匹配給定的Regex的子字串。
String replaceAll(String regex, String replacement)
按字典順序比較兩個字串,不考慮大小寫。
int compareToIgnoreCase(String str)
- 如果參數字串等於此字串,則傳回值 0;
- 如果此字串小於字串參數,則返回一個小於 0 的值;
- 如果此字串大於字串參數,則返回一個大於 0 的值。
本文參考《Java程式效能最佳化》葛一鳴著