StringBuffer为啥比String快

问题

很多初学者都被问到一个问题,String和StringBuffer有什么区别。然后就有了标准答案:

String是不可变字符串,StringBuffer是不可变字符串,在进行字符串操作时,StringBuffer效率更高。这要从字符串的不变性说起。

字符串不变性

字符串在编程过程中是如此重要,因为程序的结果主要以文本形式(也就是字符串)展现,字符串本身也是程序运行的数据表示(还是因为人呢习惯使用文字描述精确信息),所以字符串就到处被使用。这么基础的东西,如何保障可靠和高效,肯定是Java设计者会考虑的。

什么不变性,看下面的代码:

String str = "zyejy";
str.concat(".com");
System.out.println(str);//输出结果是zyejy

这就是不变性:对字符串的任何操作都不会概念被操作的字符串。

那么问题来了:为什么。还是因为可靠性和性能。

可靠性的例子

字符串String经常作为参数在程序中传递,意味着一个字符串可能在很多地方被引用,比如JVM解析类相关信息的时候也是需要使用String保存信息的,比如类的名称。如果某个地方不小心修改了类名对应的字符串的值,那么可能导致所有对这个类的依赖都会有错误,然后整个程序就GG了。不变的字符串就不会有这个问题了,因为除了实例化时能修改内容,其他任何代码都无法修改一个实例化好的字符串的内容。这是字符串不变性的第一个好处。

天然的多线程安全

因为不可以被概念,多个线程操作字符串时不会导致并发问题,不可变的字符串意味着是天然的线程安全的。

可以缓存hashCode

我们打开String的源码会发现hashCode方法是会缓存的。hashCode的实现如下:

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

 

如果上面的计算过程经常执行,显然是一个不小的计算量,不变性保障了hashCode缓存的可能。这也是为什么很多时候我们使用String作为HashMap的key。

可以缓存

不变意味着可以缓存,需要的时候可以随时拿出来使用。Java中的字符串是有缓存机制的,下面的代码输出的结果是true,这意味着编译器有能力优化字符串的内存占用,让所有内容相同的字符串只需要创建一份:

String str1 = "octopus";
String str2 = "octopus";
// 所有编译前能明确的字符串都会自动优化到字符串缓存池
System.out.println(str1 == str2);

这样,性能也有提高,因为在编译期很多字符串的内容是可以确定的,此时编译器会自动优化减少内存占用和计算资源。

 

回到问题:StringBuffer为啥比String块

因为String的不变会导致字符串操作过程中不断创建字符串,大家可以试着执行下面的代码:

String str1 = "";
for (int i = 0; i < 100000; i++) {
    str1 = str1 + ",java";
}

 

你会发现很久这段代码都执行不完,尽管只有区区10万次的循环,因为不变性导致任何对字符串的操作都会产生新的字符串。产生新的字符串需要做大量的工作:内存空间分配,内容复制这些都是非常占资源的。最关键的是,这个操作产的空间占用和复制操作是快速增长的:

第一次我们需要5个字符的空间,第二次需要5+10个字符空间(原来的字符串并不能马上被销毁掉,这是Java的特点:gc,C++完全可以通过代码释放),第n次需要5 * (1 + 2 + ... +n)也就是n 的平方级别的字符空间并进行反复操作,就好像一个1米8的人在舞动一把18米长的刀(请脑补),上面的10万会变成多少呢?大家可以计算下(当然gc会工作的,只是很慢而已)。

所有String进行操作时,对于大字符串效率是很低的。

但是我们在日常代码中是不是需要操作字符串呢?显然是需要的,比如序列化一个对象为json,或者是本身长文本的处理。

这个时候就可以考虑StringBuffer了,你可以认为这不是一个字符串,而是一个字符串操作类。因为它主要提供各种操作,而操作的方式,是在所谓的Buffer也就是缓存的的一个字符数组中操作。虽然StringBuffer还是需要根据操作执行内存的调整,但是算法上可以优化不会经常调整,并且内存增长是n级别的也就是线性级的。字符串长度长大10万倍,内存增加10万倍就可以,在这里是50万个字符,这对现代的计算机不算什么。下面的代码在我的笔记本上1秒钟内可以完成:

StringBuffer sb1 = new StringBuffer();
for (int i = 0; i < 10000000; i++) {
    sb1.append(",java");
}

 

 

那个小弟是干嘛的

StringBuilder是JDK5引入的StringBuffer的小弟,他们唯一的区别在前者不是线程安全的,实际计算效率大概有1倍以上的差距。基本上,如果你在方法内部使用字符串拼接,StringBuilder是安全的。至于线程安全,有时间我们再细聊。

欢迎大家加我QQ交流学习Java技术849437360。感兴趣的童鞋们可以互相关注哦。

posted @ 2019-05-13 21:36  huangwu  阅读(484)  评论(0编辑  收藏  举报