从底层彻底搞懂String,StringBuilder,StringBuffer的实现( 三 )


从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
而且通过StringBuilder和StringBuffer继承自同一个父类这点, 我们可以推断出它俩的方法都是差不多的. 通过查看源码也发现确实如此, 只不过StringBuffer在方法上添加了 synchronized关键字, 证明它的方法绝大多数方法都是线程同步方法. 也就是说在多线程的环境下我们应该使用StringBuffer以保证线程安全, 在单线程环境下我们应使用StringBuilder以获得更高的效率 。
既然如此, 我们的比较也就落到了StringBuilder和String身上了 。
关于StringBuilder和String之间的讨论通过查看StringBuilder和String的源码我们会发现两者之间一个关键的区别: 对于String, 凡是涉及到返回参数类型为String类型的方法, 在返回的时候都会通过new关键字创建一个新的字符串对象; 而对于StringBuilder, 大多数方法都会返回StringBuilder对象自身 。
从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
就因为这点区别, 使得两者在操作字符串时在不同的场景下会体现出不同的效率 。
下面还是以拼接字符串为例比较一下两者的性能:
从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
就拼接5万次字符串而言, StringBuilder的效率是String类的956倍 。
我们再次通过反编译代码看看造成两者性能差距的原因, 先看String类. (为了方便阅读代码, 我删除了计时部分的代码, 并重新编译, 得到的main方法反编译代码如下)
从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
从反汇编代码中可以看到, 当用String类拼接字符串时, 每次都会生成一个StringBuilder对象, 然后调用两次append()方法把字符串拼接好, 最后通过StringBuilder的toString()方法new出一个新的字符串对象 。
也就是说每次拼接都会new出两个对象, 并进行两次方法调用, 如果拼接的次数过多, 创建对象所带来的时延会降低系统效率, 同时会造成巨大的内存浪费. 而且当内存不够用时, 虚拟机会进行垃圾回收, 这也是一项相当耗时的操作, 会大大降低系统性能 。
下面是使用StringBuilder拼接字符串得到的反编译代码:
从底层彻底搞懂String,StringBuilder,StringBuffer的实现

文章插图
 
可以看到StringBuilder拼接字符串就简单多了, 直接把要拼接的字符串放到栈顶进行append就完事了, 除了开始时创建了StringBuilder对象, 运行时期没有创建过其他任何对象, 每次循环只调用一次append方法. 所以从效率上看, 拼接大量字符串时, StringBuilder要比String类给力得多 。
当然String类也不是没有优势的, 从操作字符串api的丰富度上来讲, String是要多于StringBuilder的, 在日常操作中很多业务都需要用到String类的api 。
在拼接字符串时, 如果是简单的拼接, 比如说 Strings="hello "+"world";, String类的效率会更高一点 。
但如果需要拼接大量字符串, StringBuilder无疑是更合适的选择 。
讲到这里, Java中的字符串背后的原理就讲得差不多, 相信在了解虚拟机操作字符串的细节后, 你在使用字符串时会更加得心应手. 字符串是编程中一个重要的话题, 本文围绕Java体系讲解的字符串知识只是字符串知识的冰山一角. 字符串操作的背后是数据结构和算法的应用, 如何能够以尽可能低的时间复杂度去操作字符串, 又是一门大学问 。
原文:https://mp.weixin.qq.com/s/2-Ror2TBEoCGW-o7tdUD0A

【从底层彻底搞懂String,StringBuilder,StringBuffer的实现】


推荐阅读