好比說咱們要處理幾十萬個的字符串的處理,可能好比字符串的正則替換,好比replace、replaceAll,字符串的拼接或添加使用 +,StringBuilder的append,字符串的分割使用split。然而每每若是咱們的數據量很小的時候,其實看不出來有任何的問題。下面咱們來看看他們的底層作了些什麼java
一、咱們先來看replace和replaceAll,根據api,replace是不支持正則表達式,replaceAll是支持正則表達式的。正則表達式
下面看下replaceAll底層實現,因爲篇幅關係,只貼部分的主要代碼api
public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceAll(replacement); }
public String replaceAll(String replacement) { reset(); boolean result = find(); //這兒主要是遍歷看是否有匹配的第一個字符,若是沒有直接返回,若是整個字符串沒有字符就直接return了 if (result) { StringBuffer sb = new StringBuffer(); do { appendReplacement(sb, replacement);//有的話對第一次find的字符進行添加 result = find();//繼續尋找,一直尋找到末尾的時候沒有了就直接跳出do循環 } while (result); appendTail(sb); return sb.toString(); } return text.toString(); }
因此replaceAll是遍歷兩次數組
String.replace 主要由兩個函數,它不支持正則替換,app
replace(CharSequence target, CharSequence replacement)//字符替換,好比「abc」一串的字符替換一個「a」,從匹配是不是「abc」來講,和下面字符替換確定計算更多一些的,具體的不貼代碼了,比較冗長。
replace(char oldChar, char newChar)//字符串替換,通常來講替換單個字符,使用它,效率高,一樣的替換replace("a","b") 和替換replace('a','b')過程是不同的,後面的效率高。
public String replace(char oldChar, char newChar) { if (oldChar != newChar) {//若是相等就直接返回了喔 int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) {// i值 定位到第一個字符,利於下面縮減替換的區間 break; } } if (i < len) { char buf[] = new char[len];//建立char數組 for (int j = 0; j < i; j++) { buf[j] = val[j];//先裝起來前面沒有須要替換的匹配過的數組 } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c;//後面的匹配替換 i++; } return new String(buf, true);//返回結果 } } return this; }
可見replace的替換char字符效率上是高replace替換字符串和replaceAll的,在效率上來講,若是處理字符串少,應該沒什麼問題的,可是若是處理超過幾十萬字符的計算那就很是耗時了,,或者替換的字符很少,能夠用replace替換char的api,或者本身實現,由於遍歷少,效率高。函數
測試效率可參考 別人寫的文章https://blog.csdn.net/zhanglianyu00/article/details/78296277測試
二、而後咱們字符串的拼接,咱們可使用加號 + 或者使用StringBuilder或StringBuffer的append,到底哪一個好。大數據
字符串的拼接 好比 String s = s1 + s2;這兒其實作了好幾步,首先看s1是否在字符靜態池中,java中有這個概念,有的話,就直接拿出來用,s2若是沒有的話,就建立,而後第二步進行字符串的拼接,這兒按照我的理解是,它底層實現,或許是先開闢一個內存,而後對s1和s2進行復制到s指向地址的內存中,這樣其實就是 一、靜態字符池中查找、建立 二、開闢內存複製 ,過程天然比stringBuilder複雜多了。ui
StringBuilder 底部有個重要的代碼this
/** * Copies an array from the specified source array, beginning at the * specified position, to the specified position of the destination array.
//註釋大概意思是 複製從指定源數組開始指定srcBegin開始複製指定須要複製的數組。翻譯有點蹩腳~
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);//主要是數組的複製,這個api底層是jni調用底層的實現
//依據我的最近學c的猜測,應該避免不了都是動態擴展內存,指針去擴充字符內容。遍歷次數我以爲認爲是1次能夠解決。
而後咱們看比較他們字符拼接效率:
public static void a() {
long starTime = new Date().getTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 500000; i++) {
sb.append(String.valueOf(i));
}
long endTime = new Date().getTime();
System.out.println("「append」號底層替換花費時間:" + (endTime - starTime));
}
public static void b() {
long starTime = new Date().getTime();
String string = new String();
for (int i = 0; i < 500000; i++) {
string += i;
}
long endTime = new Date().getTime();
System.out.println("「+=」號花費時間:" + (endTime - starTime));
}
「append」號底層替換花費時間:47
「+=」號花費時間:3598
在我機器上運行是這樣 StringBuilder拼接耗時47毫秒和 加號是3000多毫秒。因此咱們儘可能使用stringbuilder。
三、最後咱們看下split,因爲String.split方法會調用到CopyOfRange方法,在大數據量的狀況下,效率很低,因此改用StringTokenizer類實現String.split的功能
StringTokenizer stringTokenizer = new StringTokenizer(dataStr,","); while(stringTokenizer.hasMoreTokens()){ String eme = stringTokenizer.nextToken(); }
大概小結這樣。