说真的,这个问题我还真是经常被问,特别是坐电梯的时候突然被同事cue,“你说说String和StringBuilder、StringBuffer到底有啥区别啊?”。我说你等我喘口气,刚刚还在群里吐槽bug呢,行吧,那我随便唠两句,别太当真哈。
昨晚加班到十一点半,眼睛都快花了,优化我们那个批量处理的代码。刚好碰到一个同事在用+拼接字符串,脑瓜子嗡嗡的,他说用StringBuilder会不会好点。我说这个你得明白String、StringBuilder还有个StringBuffer,光看名字就知道是一家子的,但是脾气可差老远。
首先String,大家平时写Java都用,String s = "hello";这种很常见吧。它有个最大特点,就是“不可变”,就是你每次改内容其实是new了一个新的对象,比如这样:
String a = "hello";
a = a + " world";
System.out.println(a); // 结果是hello world
你以为a变了,其实是new了一个新的String,原来的"hello"还在内存里躺着。所以你要是for循环里一直拼接字符串,用String,老铁那内存分分钟教你做人,GC都看不下去,频繁new对象。
StringBuilder——单线程的拼接狂魔
然后就有聪明人搞出来StringBuilder,这个家伙是个“可变对象”,它内部其实就是个char数组,有个length,append的时候直接往后塞,不new新对象。举个栗子:
StringBuilder sb = new StringBuilder();
sb.append("hello");
sb.append(" world");
System.out.println(sb.toString()); // hello world
每次append都不会new新的对象,效率嗖嗖的,拼接字符串首选啊。缺点就是它不是线程安全的,多线程里别用它,不然容易数据乱套。
StringBuffer——老派线程安全选手
再说StringBuffer,这货历史悠久,Java早期就有,和StringBuilder用法差不多,也是append啥的,但所有方法都加了synchronized,所以它线程安全,多个线程一起用不会乱,但相应地效率没StringBuilder高,锁嘛,有点性能损耗:
StringBuffer sbf = new StringBuffer();
sbf.append("hello");
sbf.append(" world");
System.out.println(sbf.toString()); // hello world
但现在一般项目里,除非有特殊要求,基本很少用它了,都推崇用StringBuilder加外部同步。
我们组那个小李,for循环里还在傻傻用String拼接,我都无语了,改成StringBuilder,性能立马就上去了。你们要是遇到频繁字符串拼接的,记住:
单线程用StringBuilder
多线程(极少)用StringBuffer,或者自己加锁+StringBuilder
真没事干才用String+拼接,偶尔几次无所谓,量大了就完蛋
还有一个小tips哈,JDK8之后,编译器会自动优化一些String拼接,比如String s = "a" + "b" + "c";会在编译期就算好,所以写死的还能凑合,动态拼接的时候就别折腾了。
哦对,刚才群里还有人问,StringBuilder和StringBuffer能不能互相转?其实没啥必要,都是toString()转String就行,想线程安全就老老实实自己加锁。