比如说我们要处理几十万个的字符串的处理,可能比如字符串的正则替换,比如replace、replaceAll,字符串的拼接或添加使用 +,StringBuilder的append,字符串的分割使用split。然而往往如果我们的数据量很小的时候,其实看不出来有任何的问题。下面我们来看看他们的底层做了些什么
1、我们先来看replace和replaceAll,根据api,replace是不支持正则表达式,replaceAll是支持正则表达式的。
下面看下replaceAll底层实现,由于篇幅关系,只贴部分的主要代码
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 主要由两个函数,它不支持正则替换,
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
2、然后我们字符串的拼接,我们可以使用加号 + 或者使用StringBuilder或StringBuffer的append,到底哪个好。
字符串的拼接 比如 String s = s1 + s2;这儿其实做了好几步,首先看s1是否在字符静态池中,java中有这个概念,有的话,就直接拿出来用,s2如果没有的话,就创建,然后第二步进行字符串的拼接,这儿按照个人理解是,它底层实现,或许是先开辟一个内存,然后对s1和s2进行复制到s指向地址的内存中,这样其实就是 1、静态字符池中查找、创建 2、开辟内存复制 ,过程自然比stringBuilder复杂多了。
StringBuilder 底部有个重要的代码
/** * 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。
3、最后我们看下split,由于String.split方法会调用到CopyOfRange方法,在大数据量的情况下,效率很低,所以改用StringTokenizer类实现String.split的功能
StringTokenizer stringTokenizer = new StringTokenizer(dataStr,",");while(stringTokenizer.hasMoreTokens()){ String eme = stringTokenizer.nextToken();}
大概小结这样。