String、StringBuffer、StringBuilder的应用场景

结论

String:不需要修改字符串,或者修改次数极少 。(String的+=其实不保证线程安全,String的+=操作底层是StringBuilder实现的,线程不安全)

StringBuffer:需要频繁修改字符串,多线程场景,需要保证线程的安全。

StringBuilder:需要频繁修改字符串,单线程场景,需要修改速度快。

原因

为什么String的访问速度快,为什么不能修改

String底层是一个常量,访问一个常量很快的,常量不支持修改。

java 复制代码
private final char value[]

既然String不能被修改,那+=是怎么回事

java 复制代码
public class Main {
    public static void main(String[] args) {
        String s = "123";
        s += "456";
        System.out.println(s);
    }
}

输出

out 复制代码
123456

这样看起来,好像是在s后面添加了字符串"456",但是实际上,是改变了s的引用

下面这段代码可以验证,s已经不是之前的s了。

java 复制代码
public class Main {
    public static void main(String[] args) {
        String s = "123";
        String b = s;
        System.out.println(b == s);
        s += "456";
        System.out.println(b);
        System.out.println(b == s);
    }
}

输出

out 复制代码
true
123
false

所以String的修改不是修改原字符串,而是替换字符创指向的引用。

真正的修改原来字符串会有下面的现象

java 复制代码
public class Main {
    public static void main(String[] args) {
        StringBuilder s = new StringBuilder("123");
        StringBuilder b = s;
        System.out.println(b == s);
        s.append("456");
        System.out.println(b);
        System.out.println(b == s);
    }
}

输出

out 复制代码
true
123456
true

验证StringBuilder线程不安全,StringBuffer线程安全

通过对比多线程场景下 StringBuilderStringBuffer 的拼接结果,验证二者的线程安全性:

一个线程负责在末尾添加"1",另一个线程负责在末尾添加"2"。

假如线程安全,那么要么全是1后面接全是2,要么全是2后面接全是1

如果结果不是这两个,那么线程不安全。

java 复制代码
public class Main {
    public static void main(String[] args) throws InterruptedException {
        int repeatCount = 100000;
        String result1 = "1".repeat(repeatCount) + "2".repeat(repeatCount);
        String result2 = "2".repeat(repeatCount) + "1".repeat(repeatCount);

        StringBuilder stringBuilder = new StringBuilder();
        Thread thread1 = new Thread(() -> {
            stringBuilder.append("1".repeat(repeatCount));
        });
        Thread thread2 = new Thread(() -> {
            stringBuilder.append("2".repeat(repeatCount));
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(stringBuilder.toString().equals(result1) || stringBuilder.toString().equals(result2));

        StringBuffer stringBuffer = new StringBuffer();
        thread1 = new Thread(() -> {
            stringBuffer.append("1".repeat(repeatCount));
        });
        thread2 = new Thread(() -> {
            stringBuffer.append("2".repeat(repeatCount));
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(stringBuffer.toString().equals(result1) || stringBuffer.toString().equals(result2));
    }
}

输出

out 复制代码
false
true

如果抛出ArrayIndexOutOfBoundsException,也能证明,ArrayIndexOutOfBoundsExceptionStringBuilder 线程不安全的典型严重表现 ,核心原因是多线程同时操作 StringBuilder 底层的字符数组时,导致数组扩容逻辑混乱,最终触发数组下标越界。

SrringBuffer线程安全的原因,StringBuilder速度快的原因

SrringBuffer对方法加了同步锁,保证了线程安全,但是限制了运行速度。

StringBuilder没有同步锁,所以线程不安全,由于没有锁的限制,执行速度却快。

面试

如果不修改或者修改次数极少,可以用String。

如果需要频繁修改,

多线程下,必须使用StringBuffer来保证线程安全。

单线程下,可以使用StringBuilder比StringBuffer快一些。

相关推荐
桂花很香,旭很美1 小时前
[7天实战入门Go语言后端] Day 5:中间件与业务分层——日志、鉴权与请求超时
开发语言·中间件·golang
日月云棠2 小时前
JDK 17 特性详解
java
追随者永远是胜利者2 小时前
(LeetCode-Hot100)19. 删除链表的倒数第 N 个结点
java·算法·leetcode·链表·go
树码小子2 小时前
Mybatis(14)Mybatis-Plus入门 & 简单使用
java·mybatis-plus
人道领域2 小时前
Maven配置加载:动态替换的艺术
java·数据库·后端
沐知全栈开发2 小时前
Python File 方法详解
开发语言
MX_93592 小时前
@Import整合第三方框架原理
java·开发语言·后端·spring
坚持就完事了2 小时前
Java实现数据结构中的链表
java·数据结构·链表
写代码的小球2 小时前
C++ 标准库 <numbers>
开发语言·c++·算法