Java中substring、split、StringTokenizer三種擷取字串方法的效能比較
阿新 • • 發佈:2019-02-12
最近在閱讀java.lang下的原始碼,讀到String時,突然想起面試的時候曾經被人問過:都知道在大資料量情況下,使用String的split擷取字串效率很低,有想過用其他的方法替代嗎?用什麼替代?我當時的回答很斬釘截鐵:沒有。
Google了一下,發現有2種替代方法,於是在這裡我將對這三種方式進行測試。
測試的軟體環境為:Windows 10、Intellij IDEA、JDK1.8。
測試用例使用類ip形式的字串,即3位一組,使用”.”間隔。資料分別使用:5組、10組、100組、1000組、10000組、100000組。
實現
閒話不說,先上程式碼:
import java.util.Random;
import java.util.StringTokenizer;
/**
* String測試類
* @author yuitang
*
*/
public class StringTest {
public static void main(String args[]){
String orginStr = getOriginStr(5);
//////////////String.split()表現//////////////////////////////////////////////
System.out.println("使用String.split()切分字串");
long st1 = System.nanoTime();
String [] result = orginStr.split("\\.");
System.out.println("String.split()擷取字串用時:" + (System.nanoTime()-st1));
System.out.println("String.split()擷取字串結果個數:" + result.length);
System.out.println();
//////////////StringTokenizer表現//////////////////////////////////////////////
System.out.println("使用StringTokenizer切分字串");
long st3 = System.nanoTime();
StringTokenizer token=new StringTokenizer(orginStr,"." );
System.out.println("StringTokenizer擷取字串用時:"+(System.nanoTime()-st3));
System.out.println("StringTokenizer擷取字串結果個數:" + token.countTokens());
System.out.println();
////////////////////String.substring()表現//////////////////////////////////////////
long st5 = System.nanoTime();
int len = orginStr.lastIndexOf(".");
System.out.println("使用String.substring()切分字串");
int k=0,count=0;
for (int i = 0; i <= len; i++) {
if(orginStr.substring(i, i+1).equals(".")){
if(count==0){
orginStr.substring(0, i);
}else{
orginStr.substring(k+1, i);
if(i == len){
orginStr.substring(len+1, orginStr.length());
}
}
k=i;count++;
}
}
System.out.println("String.substring()擷取字串用時:"+(System.nanoTime()-st5));
System.out.println("String.substring()擷取字串結果個數:" + (count + 1));
}
/**
* 構造目標字串
* eg:10.123.12.154.154
* @param len 目標字串組數(每組由3個隨機陣列成)
* @return
*/
private static String getOriginStr(int len){
StringBuffer sb = new StringBuffer();
StringBuffer result = new StringBuffer();
Random random = new Random();
for(int i = 0; i < len; i++){
sb.append(random.nextInt(9)).append(random.nextInt(9)).append(random.nextInt(9));
result.append(sb.toString());
sb.delete(0, sb.length());
if(i != len-1)
result.append(".");
}
return result.toString();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
改變目標資料長度修改getOriginStr的len引數即可。
5組測試資料結果如下圖:
Function | 5 | 10 | 100 | 1000 | 10000 | 100000 |
---|---|---|---|---|---|---|
split | 45792 | 50134 | 155534 | 1035446 | 5395136 | 23128381 |
StringTokenizer | 24080 | 24870 | 41845 | 62766 | 44608 | 30002 |
substring | 35528 | 37896 | 156324 | 838463 | 8575293 | 25692324 |
下面這張圖對比了下,split耗時為substring和StringTokenizer耗時的倍數:
Tables | split | StringTokenizer | substring |
---|---|---|---|
5 | 1.90 | 1.29 | |
10 | 2.02 | 1.32 | |
100 | 3.17 | 0.99 | |
1000 | 16.50 | 1.23 | |
10000 | 120.95 | 0.63 | |
100000 | 770.89 | 0.90 |
結論
最終,StringTokenizer在擷取字串中效率最高,不論資料量大小,幾乎持平。substring和split的效率幾乎差別不大,甚至當資料量足夠龐大的時候,substring的效率還比不上split。
究其原因,split的實現方式是採用正則表示式實現,所以其效能會比較低。至於正則表示式為何低,還未去驗證。split原始碼如下:
public String[] split(String regex) {
return split(regex, 0);
}
public String[] split(String regex, int limit) {
/* fastpath if the regex is a
(1)one-char String and this character is not one of the
RegEx's meta characters ".$|()[{^?*+\\", or
(2)two-char String and the first char is the backslash and
the second is not the ascii digit or ascii letter.
*/
char ch = 0;
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE))
{
int off = 0;
int next = 0;
boolean limited = limit > 0;
ArrayList<String> list = new ArrayList<>();
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
off = next + 1;
} else { // last one
//assert (list.size() == limit - 1);
list.add(substring(off, value.length));
off = value.length;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};
// Add remaining segment
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
// Construct result
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
return Pattern.compile(regex).split(this, limit);
}