最近在阅读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);
}