String与StringBuffer效率对比
昨天申请了一个LeetCode的账号,先刷了一题最基础的,字符串逆序输出。
我先写出了如下代码:
public class Solution { public String reverseString(String s) { String rev = ""; for(int i = s.length()-1; i >= 0; i--){ rev += s.charAt(i); } return rev; } }
这份代码在OJ上运行的结果是Time Limit Exceeded,也就是超时了,显然效率太低。
我又改成如下代码,把String类换成StringBuffer类:
public class Solution { public String reverseString(String s) { StringBuffer rev = new StringBuffer(); for(int i = s.length()-1; i >= 0; i--){ rev.append(s.charAt(i)); } return rev.toString(); } }
这份代码可以通过,并且用时只有6ms。
为什么String类和StringBuffer类的效率会相差这么多呢?我上网查了资料,主要是以下原因。
String类的对象其实是一个常量,例如String s = "abc"; 那么s其实是一个常量,它的值就是"abc",不能改变。
Java所提供的String操作方法,比如直接进行 '+' 运算,其实是作了封装,给了使用者能对String对象进行增加或删除的错觉,本质上并不是如此。
当我们执行 s += "def"; 时,并不是说 s 的内容直接由"abc"变为了"abcdef","abc"仍是"abc","def"会被暂时存储到一块新的内存里,然后再开辟一个新的内存空间,把"abc"和"def"拷贝到这个内存空间,然后 s 指向这个新的内存空间。
这个过程其实就是new了一个新的String对象,新对象是"abcdef",s 抛弃了原来的对象,指向了新对象。
由于每次操作都要重新new一个对象,并且占用大量内存,因此String类的效率不高。
那么StringBuffer呢?
StringBuffer对象是一个长度可变的对象,当对StringBuffer的进行增加或删除时,不需要new一个对象,可以动态地修改堆内存。
StringBuffer提供了append方法,在字符串的末尾进行添加。实际上,String类进行 '+' 运算,就是创建了一个StringBuffer对象,然后调用append进行添加,最后调用toString把StringBuffer转化为String。
显然,String的效率绝对不如StringBuffer了。
可以再看一个直观的测试样例:
public static void main(String[] args){ String toappend = "abcdefghijklmnopqrstuvwxyz";//26个字母,用于每次添加 int times = 20000;//添加20000次 //使用String类,将26个字母添加20000次 long start = System.currentTimeMillis(); String str = ""; for(int i = 0; i < times; i++){ str += toappend; } long end = System.currentTimeMillis(); System.out.println("String time = " + (end-start)+" ms");
//使用StringBuffer类,将26个字母添加20000次 start = System.currentTimeMillis(); StringBuffer strbuf = new StringBuffer(); for(int i = 0; i < times; i++){ strbuf.append(toappend); } end = System.currentTimeMillis(); System.out.println("StringBuffer time = " + (end-start)+" ms"); }
运行结果: