String、StringBuffer、StringBuild的区别与联系

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

1.简单的分析

string: 点开源码。string用的是final类型,表明这个字符串不可变,并且实现了java.io.Serializable, Comparable, CharSequence三个方法。final类表明不能被继承、fina表示成员变量,一旦被修改赋值,就不能再次赋值,只能赋值一次。final方法不能被覆盖,但是能被继承。
String、StringBuffer、StringBuild的区别与联系_第1张图片
stringbuffer和stringbuild: 点开源码。我们可以明显的看到这这块方法里面加了个同步锁,这个后面会说到加锁的意义。stringbuffer继承了AbstractStringBuilder类,实现了两个方法,这个和stringbuild都是一样的,他们两个在代码上的区别就是 : stringbuffer的append方法加锁和加了一个toStringCach清空缓存的方法。 而stringbuild这两个都没有。
String、StringBuffer、StringBuild的区别与联系_第2张图片
String、StringBuffer、StringBuild的区别与联系_第3张图片
String、StringBuffer、StringBuild的区别与联系_第4张图片
下面是类的关系继承图:
String、StringBuffer、StringBuild的区别与联系_第5张图片

String、StringBuffer、StringBuild的区别与联系_第6张图片

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 Runnable is used
         * to create a thread, starting the thread causes the object's
         * run method to be called in that separately executing
         * thread.
         * 

* The general contract of the method run 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。

你可能感兴趣的:(java)