【Java面试题】Java中String、StringBuffer、StringBuilder 的区别?

Java中String、StringBuffer、StringBuilder 的区别

  • 1、前言
  • 2、三者异同之处
    • 2.1、是否可变?
    • 2.2、线程是否安全?
    • 2.3、StringBuilder与StringBuffer共同点
    • 2.4、StringBuilder与StringBuffer的不同点
  • 3、三者案例分析
    • 3.1、速度方面
    • 3.2、线程安全方面
    • 3.3、小总结
  • 4、综合总结

1、前言

我们知道String是最常用的一个字符串类,但是问到String、StringBuffer、StringBuilder 三者的区别时,往往会着重区别StringBuffer与StringBuilder的区别,因为三者都是final修饰的常量类,而String字符串的定义类中@Stable private final byte[] value;从这可以看出,String字符串对象一旦创建后值就不可更改,也就是每次操作字符串的时候,其实都是new的一个新的字符串进行的操作,可想而知在有大量操作的场景下效率是极低的,因此才会引申出StringBuffer与StringBuilder类的操作(对象值可修改)。

那问到StringBuffer与StringBuilder的区别时,张口就来StringBuffer是线程安全的,因为StringBuffer相关的方法都是加了synchronized关键字(同步锁),而StringBuilder是线程不安全的。此处还有一点,看过源码的伙子应该知道StringBuffer是从JDK1.0就开始用了,而StringBuilder是从JDK1.5才开始引入的,所以好好想想既然有了StringBuffer,为什么还要从JDK1.5引入StringBuilder呢?最值的研究的当然就是两者在单线程下的区别了!

2、三者异同之处

java中用于处理字符串常用的有三个类:

java.lang.String
java.lang.StringBuffer
java.lang.StrungBuilder

三者共同之处:都是final类,不允许被继承,主要是从性能安全性上考虑的,因为这几个类都是经常被使用,且考虑到防止其中的参数被修改影响到其他线程的应用。

2.1、是否可变?

  • String:字符串常量,@Stable private final byte[] value;在修改时不会改变自身;若修改,等于重新生成新的字符串对象。
  • StringBuilderStringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value;,没有final修饰,可知这两种对象都是可变的,在修改时会改变对象自身,每次操作都是对对象本身进行修改,不是生成新的对象;使用场景:对字符串经常改变情况下,主要方法:append(),insert()等。

2.2、线程是否安全?

  • String: String对象的值是不可变的,值为常量,显然线程安全
  • StringBuffer: 对象值虽然可以改变,但是StringBuffer对对所调用的父类中的方法都加了同步锁synchronized,所以是线程安全的,源码如下:
    @Override
    @HotSpotIntrinsicCandidate
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    
    @Override
    public synchronized StringBuffer insert(int offset, String str) {
       	toStringCache = null;
        super.insert(offset, str);
        return this;
    }
    
  • StringBuilder: 并没有对方法进行加同步锁,所以是非线程安全的。

2.3、StringBuilder与StringBuffer共同点

  • 都是继承自公共抽象父类AbstractStringBuilder
  • 都会调用父类AbstractStringBuilder中的公共方法,如:super.append()、super.insert()等方法;
  • 初始容量大小都是16,扩容机制都是旧容量*2 + 2;
  • 底层都是用char[]字符数组实现,且字符数组都是可变的,这点不同于String

2.4、StringBuilder与StringBuffer的不同点

  • StringBuilder是非线程安全的,StringBuffer是线程安全的
  • StringBuilder是从JDK1.5才引入的,StringBuffer是从JDK1.0就开始使用了
  • StringBuffer比StringBuilder多了一个toStringCache字段,用来在toString方法中进行缓存,每次append操作之前都会先把toStringCache设置为null值;若多次连续调用toString方法,可以避免每次Arrays.copyOfRange(value,0,count)复制值数组下标操作,节省性能。
    	@Override
        public synchronized StringBuffer append(Object obj) {
            toStringCache = null;
            super.append(String.valueOf(obj));
            return this;
        }
    
    	@Override
        @HotSpotIntrinsicCandidate
        public synchronized String toString() {
            if (toStringCache == null) {
                return toStringCache =
                        isLatin1() ? StringLatin1.newString(value, 0, count)
                                   : StringUTF16.newString(value, 0, count);
            }
            return new String(toStringCache);
        }
    
  • StringBuffer考虑到线程安全同步,对所调用的方法都加上了同步锁synchronized,而StringBuilder没有考虑同步,在单线程情况下,StringBuilder的性能要优于StringBuffer。

3、三者案例分析

这三个类之间的最主要区别是在两个方面,即运行速度线程安全这两方面。

3.1、速度方面

首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:

StringBuilder > StringBuffer > String

String最慢的原因:String为字符串常量,而StringBuffer和StringBuilder为字符串变量,及String对象一旦创建之后,字符串对象值是不可更改的,但是后两者的对象值是可以更改的,以下为一段代码实例:

public class Test1 {
    public static void main(String[] args) {
        String str = "abc";
        System.out.println(str); //abc
        str = str + "def";
        System.out.println(str); //abcdef
    }
}

上述这段代码会先输出字符串“abc”,然后输出字符串“abcdef”,表面上看似str这的对象被更改了,其实不然,只是一种假象罢了。JVM对于这几行代码是这样处理的,首先是创建一个String对象str,并把“abc”赋值给str,然后在第五行str = str + "def";中,其实JVM有创建了一个新的str对象,然后把原来的str对象和“def”拼接后的值赋给了新的str,而原来的str在第四行执行完输出后,就会被JVM的垃圾回收机制(GC)给回收掉了,所以实际上str并没有被更改,这就是前面说的String字符串对象一旦创建就不可更改了,所以Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度会很慢。

而StringBuffer和StringBuilder的对象是变量,对变量的操作就是直接对该对象的值进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

public class Test1 {
    public static void main(String[] args) {
        String string = "abc" + "def";
		StringBuilder stringBuilder = new StringBuilder().append("abc").append("def");
		System.out.println(string); //abcdef
		System.out.println(stringBuilder.toString());//abcdef
    }
}

上述两种输出的结果都是“abcdef”,但是在上述代码中String的速度却比StringBuilder的反映速度要快很多,这是因为String string = "abc" + "def";这行代码和String str = '"abcdef"完全是一样的,所以会很快,而如果写成下面这种形式:

String str1="abc";
String str2="def";
String str=str1+str2;

那么JVM中就会像上述所说的那样不断的进行创建、回收操作了,很显然速度就慢下来了。

接下来我们直接测试三者的对比:

package com.test.test;

import com.test.pojo.People;

/**
 * @author 一宿君(CSDN : qq_52596258)
 * @date 2021-09-24 09:16:07
 */
public class Test1 {
    public static void main(String[] args) {
        /**
         * String操作拼接
         */
        String str = "";
        Long t1 = System.currentTimeMillis();
        for(int i=0;i<10000;i++){
            str += i;
        }
        System.out.println("String字符串拼接消耗的时间:" + (System.currentTimeMillis() - t1)/1000.0 + "S");


        /**
         * StringBuffer操作拼接
         */
        StringBuffer stringBuffer = new StringBuffer();
        Long t2 = System.currentTimeMillis();
        for(int i=0;i<10000;i++){
            stringBuffer.append(i);
        }
        System.out.println("StringBuffer字符串拼接消耗的时间:" + (System.currentTimeMillis() - t2)/1000.0 + "S");

        /**
         * StringBuilder操作拼接
         */
        StringBuilder stringBuilder = new StringBuilder();
        Long t3 = System.currentTimeMillis();
        for(int i=0;i<10000;i++){
            stringBuilder.append(i);
        }
        System.out.println("StringBuilder字符串拼接消耗的时间:" + (System.currentTimeMillis() - t3)/1000.0 + "S");

    }
}

输出结果:

String字符串拼接消耗的时间:0.198S
StringBuffer字符串拼接消耗的时间:0.002S
StringBuilder字符串拼接消耗的时间:0.001S

知道谁快了吧!!!

3.2、线程安全方面

在线程安全上,StringBuilder是非线程安全的,而StringBuffer是线程安全的。

如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法都带有synchronized关键字,所有可以保证线程是安全的,但是StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作,所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程下,还是建议使用速度较快的StringBuilder。

一个线程访一个对象中的synchronized(this)同步代码块时,其他线程访问该对象将被阻塞。

3.3、小总结

  • String:适用于少量的字符串操作的情况;
  • StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况;
  • StringBuffer:适用于多线程下在字符缓冲区进行大量操作的情况。

4、综合总结

String和StringBuffer、StringBuilder的主要区别在于String声明的是不可变的对象,每次操作字符串都会生成一个新的String对象,然后将指针指向了新的String对象,而StringBuffer和StringBuilder可以在原有的对象的值上进行操作,所以在经常在改变字符串内容的情况下最好不要使用String。

StringBuffer和StringBuilder最大区别在于,StringBuffer是线程安全的,而StringBuilder是非线程安全的,但StringBuilder的性能却高于StringBuffer,所以在单线程下推荐使用StringBuilder,在多线程下推荐使用StringBuffer。


在这里插入图片描述

一起学编程,让生活更随和!如果你觉得是个同道中人,欢迎关注博主公众号:【随和的皮蛋桑】。专注于Java基础、进阶、面试以及计算机基础知识分享。偶尔认知思考、日常水文。

在这里插入图片描述

你可能感兴趣的:(Java编程技术,String,StringBuffer,StringBuilder)