这是一个高频考点,一般面试也会遇到,接下来我们会从源码角度区分这三个的区别和实用场景。
1.简单的分析
string: 点开源码。string用的是final类型,表明这个字符串不可变,并且实现了java.io.Serializable, Comparable, CharSequence三个方法。final类表明不能被继承、fina表示成员变量,一旦被修改赋值,就不能再次赋值,只能赋值一次。final方法不能被覆盖,但是能被继承。
stringbuffer和stringbuild: 点开源码。我们可以明显的看到这这块方法里面加了个同步锁,这个后面会说到加锁的意义。stringbuffer继承了AbstractStringBuilder类,实现了两个方法,这个和stringbuild都是一样的,他们两个在代码上的区别就是 : stringbuffer的append方法加锁和加了一个toStringCach清空缓存的方法。 而stringbuild这两个都没有。
下面是类的关系继承图:
2.源码解析append方法的过程
因为上面我们说过stringbuffer和stringbuild的源码都是差不多的,所以,这里我们拿stringbuffer过来讲解,里面主要牵涉到这几个方法:
toStringCache:缓存最后一次tostring的值,每次进行append的时候清空这个值
append方法:
public AbstractStringBuilder append(String str) {
if (str == null)
//如果等于空,直接返回
return appendNull();
//获取字符串的长度
int len = str.length();
//count表明是数组中已经使用的字符个数,是个变量
ensureCapacityInternal(count + len);
//因为上面赋值了新的value数组,将当前字符串str从0到(len-count)位置上的字符复制到字符数组value中,并从value的count处开始存放
str.getChars(0, len, value, count);
count += len;
return this;
}
ensureCapacityInternal方法:
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
//minimumCapacity 需要的容量。value.length目前最大存储容量
//如果超过了最大存储容量,则进行扩容
if (minimumCapacity - value.length > 0) {
//newCapacity正常情况下 返回原来的两倍length
//Arrays.copyOf 复制原数据到新的数组中
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
newCapacity方法:
private int newCapacity(int minCapacity) {
// overflow-conscious code
//赋值原容量的两倍并且加2
int newCapacity = (value.length << 1) + 2;
//这个一般情况下不会走,除非有最大值情况
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
3.效率与安全测试
由下面数据可以看出:
拼接效率: stringbuild>stringbuffer>string
线程安全问题: 多线程情况下不能使用stingbuild,会出现意想不到的情况
package com.list.one.demo.controller;
import java.util.stream.Stream;
/**
* @Author: Mr sheng.z
* @Description: Stringbuffer和stringbuild线程安全测试
* @Date: Create in 9:30 2019/9/4
*/
public class BufferAndBuildTest {
public static class Str implements Runnable {
StringBuilder stringBuilder = new StringBuilder();
StringBuffer stringBuffer = new StringBuffer();
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
stringBuilder.append("A");
stringBuffer.append("A");
if (i == 9) {
System.out.println("stringBuilder|" + stringBuilder);
System.out.println("stringBuffer |" + stringBuffer);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
// Str str = new Str();
// new Thread(str).start();
// new Thread(str).start();
//效率测试
String a = "";
Long start1 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
a = a + i;
}
Long end1 = System.currentTimeMillis();
System.out.println("拼接String耗时:"+(end1-start1)+"毫秒");
StringBuffer stringBuffer=new StringBuffer();
Long start2 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
stringBuffer.append(i);
}
Long end2 = System.currentTimeMillis();
System.out.println("拼接stringBuffer耗时:"+(end2-start2)+"毫秒");
StringBuilder stringBuilder=new StringBuilder();
Long start3 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
stringBuilder.append(i);
}
Long end3 = System.currentTimeMillis();
System.out.println("拼接stringBuilder耗时:"+(end3-start3)+"毫秒");
//多线程要多测试几次
//stringBuilder|A AAAAAAAAAAAAAAAAAA
//stringBuilder|A AAAAAAAAAAAAAAAAAA
//stringBuffer |AAAAAAAAAAAAAAAAAAAA
//stringBuffer |AAAAAAAAAAAAAAAAAAAA
//拼接String耗时:781毫秒
//拼接stringBuffer耗时:11毫秒
//拼接stringBuilder耗时:1毫秒
}
}
4.总结
1、常量的声明,少量的字符串操作,用string。
2、在单线程情况下,如有大量的字符串操作情况,应该使用StringBuilder来操作字符串。不能使用String”+”来拼接使用,耗费空间且执行效率低下(新建对象、JVM垃圾回收大量时间)。
3、在多线程情况下,如有大量的字符串操作情况,应该使用StringBuffer。
4.stringbuild不是线程安全的,stringbuffer和string都是线程安全的。
5.stringbuffer和stringbuild默认初始容量都是16。