这是一个高频考点,一般面试也会遇到,接下来我们会从源码角度区分这三个的区别和实用场景

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。


版权声明:本文为Z__Sheng原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Z__Sheng/article/details/100538975