java String 和 StringBuilder 和 StringBuffer

关于此三者的一些问题,谈一谈我的认识。

感谢:https://www.zhihu.com/question/20101840 和 http://www.cnblogs.com/ITtangtang/p/3976820.html

 

一、有什么区别

String就不用特别解释了,这个东西大家都知道有什么特点。

StringBuffer是线程安全的,有加锁开销,效率略低。

StringBuilder非线程安全,不用加锁,效率更高。

二、适用场景

1.使用String类的场景

在字符串不经常变化的场景中可以使用String类,例如常量的声明、少量的变量运算。

2.使用StringBuffer类的场景(存在争议,具体看第三节)

在频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装。

但是,有什么场景需要这样使用呢?我没有想到…

3.使用StringBuilder类的场景

在频繁进行字符串运算(如拼接、替换、和删除等),并且运行在单线程的环境中,则可以考虑使用StringBuilder,如SQL语句的拼装、JSON封装等。

三、编译器对拼接String的处理

1.拼接String

拼接String时,有时候会遇到这样的情况:

javap一下,可以发现编译器大致做了如下处理:

1

每次拼接(每次“+”)都会new一个StringBuilder,通过获取String的值,对StringBuilder进行初始化,之后调用append方法进行拼接,最后调用toString方法得到了String。

执行完这段程序,我们需要的只是一个string对象而已,拼接过程中创建的所有StringBuilder对象都会被GC回收。因为一共进行了两次拼接,所以产生了2个需要被回收的StringBuilder对象。

在这种情况下,不如一开始就使用StringBuilder进行拼接:

这种做法实现了同样的效果,还减少了一次中间对象的生成。拼接的次数越多,StringBuilder的优势就会越明显。

但是有这样一种情况:

在这里只有一个String对象:

1

可以看到String string = “xie” + “4” + “ever”被优化,等价于:String string = “xie4ever”,没有连加操作,不需要StringBuilder优化。

2.为什么编译器不跳过中间的StringBuilder对象呢?

既然拼接时创建的中间对象StringBuilder总是要被回收的,那么编译器为什么不跳过中间的StringBuilder对象,进行优化呢?

作为程序的编写者,我们知道当前string对象并没有拼接完,所以会这么想。但是编译器并没有这么智能,在编译到string += “4”时,编译器不知道后面还有string += “ever”,所以无法采取相应的优化。

如果我们想让编译器知道后面的内容(还有string += “ever”),可能要对代码做一次全局处理,编译器才能知道后面还有拼接操作,从而对这种代码进行优化。问题是,全局处理和直接相加两种操作一比较,不仅实现难度大,还更加耗费资源。所以,想让编译器进行这种优化代价太大,得不偿失。

编译器一直倾向于解决眼前的问题。如果我们想要优化这个过程,只能手动更换更聪明的写法。

3.联想到return

关于减少中间对象的创建,可以联想到这两种return方式:

使用return2方法返回的结果是一样的,但是可以少回收一个String对象,在理论上是更好的选择。

4.在多次循环条件下,必须使用StringBuilder

在没有多次循环的情况下,下面两种代码的运行机制是相同的:

但是,一旦存在多次循环,add1每次相加,都需要创建一个新的StringBuilder对象,再进行append。也就是说,循环多少次,就要创建多少个StringBuilder对象,这些对象全部都需要GC回收。

add2在new环节就创建了StringBuilder对象,之后每次都是直接进行append,最后只需要回收一个StringBuilder对象即可。

总之,如果没有循环,怎么加都没事。如果有循环,那就用StringBuilder。

四、基本上用不到的“StringBuffer”

1.下面是知乎网友的观点:

StringBuffer固然是线程安全的,StringBuffer固然是比StringBuilder更慢,固然,在多线程的情况下,理论上是应该使用线程安全的StringBuffer的。

然而,然而,然而,有谁给我一个实际的案例来显示你需要一个线程安全的String拼接器?对不起,至少在我浅薄的十几年编程生涯中还没有遇到过,也许,仅仅是也许,这个地球上的确是存在这样的编程需求的,然而,它至少跟99.99…99%的程序员是无关的。

所以,StringBuffer基本没有适用场景,你应该在所有的情况下选择使用StringBuiler,除非你真的遇到了一个需要线程安全的场景,如果遇到了,请务必在这里留言通知我。

然后,补充一点,关于线程安全,即使你真的遇到了这样的场景,很不幸的是,恐怕你仍然有99.99….99%的情况下没有必要选择StringBuffer,因为StringBuffer的线程安全,仅仅是保证jvm不抛出异常顺利的往下执行而已,它可不保证逻辑正确和调用顺序正确。

大多数时候,我们需要的不仅仅是线程安全,而是锁。

2.我的理解

没有什么业务场景会需要多线程去拼接字符串…所以基本没有必要使用线程安全的StringBuffer。

假设有这样一个需求,一群人协作写一段文字(因为有多人同时对一个对象进行操作,所以涉及到线程安全)。这个需求完全可以使用StringBuilder实现(使用锁或者在StringBuilder对象上加synchronized),还是没有必要使用StringBuffer。

3.为什么会有StringBuffer的存在?

如果真的没有价值,为什么jdk会提供这个类?

答案太简单了,因为最早是没有StringBuilder的,sun的人不知处于何种愚蠢的考虑,决定让StringBuffer是线程安全的,然后大约10年之后,人们终于意识到这是一个多么愚蠢的决定,意识到在这10年之中这个愚蠢的决定为Java运行速度慢这样的流言贡献了多大的力量。

于是,在jdk1.5的时候,终于决定提供一个非线程安全的StringBuffer实现,并命名为StringBuilder。

五、总结

以后面试的时候,只要问到这三者的区别,大可以这样回答:

只需要用String和StringBuilder,StringBuffer是Sun脑洞下的产物,几乎不存在适用场景。

1 对 “java String 和 StringBuilder 和 StringBuffer”的想法;

发表评论

电子邮件地址不会被公开。 必填项已用*标注