String,StringBuilder,StringBuffer 源码分析

 2023-01-30
原文作者:蒋先森 原文地址:https://jlj98.top/

声明:本文使用JDK1.8

在Java中,对于字符串的操作有这三种:String、StringBuilder、StringBuffer。这三者的效率是:StringBuilder > StringBuffer > String。

    String a = "abc";
    a = a + "d";
    System.out.println(a);
    StringBuffer buffer = new StringBuffer();
    buffer.append("a");
    System.out.println(buffer);
    StringBuilder builder = new StringBuilder();
    builder.append("b");
    System.out.println(builder);

String

先来看下String的源码,如图所示:

202212301148394591.png

从图中我们可以看出,String 是由 char 数组构成的,而且有 final 关键字修饰,这说明 String 类型的对象是不可以改变的。那么,平时我们使用“+”来拼接字符串是什么实现的?

如上面的代码,首先创建一个 String 对象 a,再把“abc”赋值给它,后面Java虚拟机又创建了一个 String 对象 a,然后再把原来的 a 的值和 “d” 加起来再赋值给新的 a,而原来的a 就会被Java虚拟机的垃圾回收机制(GC)给回收掉了,所以,a 实际上并没有被更改,也就是前面说的 String 对象一旦创建之后就不可更改了。从这里可以看出,对于频繁操作的字符串,不建议使用 String 类型,这将会是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

对于 String 类型对象的“+”操作,通过在 StringBuilder 的 append 方法上面打断点,可以发现对于String的操作,其实是使用了 StringBuilderappend 操作,这个不是线程安全。详细可以看下面关于 StringBuilder 的源码。

202212301148406262.png

202212301148415703.png

String 的性能差,主要是对于频繁操作的字符串,不断的创建和销毁对象,对于程序性能有很大的影响。

StringBuffer

202212301148422924.png

从源码中我们可以看出,对 StringBuffer 来说,底层也是 char 数组。StringBuffer 默认初始空间是16。对于 StringBuffer 的扩容,从下面的这张图上面可以看出,是在旧的数组的2倍上面,再加2进行扩容。

202212301148435925.png

下面我们在来看下 StringBuffer 的操作函数appendappend 方法是由 synchronized 修饰的,是线程安全的。

202212301148446366.png

StringBuilder

通过和 StringBuffer 的源码比较,我们发现,底层也是char数组,初始空间也是16。StringBuilderStringBuffer 都是从 AbstractStringBuilder 继承来的,所以对于其初始空间和扩容都是相同的。

202212301148454487.png

对于 StringBuilderStringBuffer 的区别可以从下面的这张图片上看出,对于append()方法,缺少了synchronized 修饰,这使得 StringBuilder 不是一个线程安全。

202212301148475388.png

三者性能比较

我做了个测试,代码如下:

    public static void main(String[] args) {
        int num = 10000;
        String a = "abc";
        long time = System.currentTimeMillis();
        for (int i = 1; i < num; i++) {
            a = a + i;
        }
        System.out.println(System.currentTimeMillis() - time);
        long time1 = System.currentTimeMillis();
        StringBuffer buffer = new StringBuffer();
        for (int i = 1; i < num; i++) {
            buffer.append(i);
        }
        System.out.println(System.currentTimeMillis() - time1);
        StringBuilder builder = new StringBuilder();
        long time2 = System.currentTimeMillis();
        for (int i = 1; i < num; i++) {
            builder.append(i);
        }
        System.out.println(System.currentTimeMillis() - time2);
    }

再来看下运行的结果:

    405
    1
    1

从运行结果,我们可以非常明显的看出这三者的性能比较。

总结

String 长度大小不可变
StringBuffer 和 StringBuilder 长度可变
StringBuffer 线程安全 StringBuilder 线程不安全
因此:
String:适用于少量的字符串操作的情况
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况