【JAVA基础】String、StringBuilder和StringBuffer的区别——巨详细

【JAVA基础】String、StringBuilder和StringBuffer的区别------巨详细

先给答案

String是不可变的,StringBuilderStringBuffer是可变的。而StringBuffer是线程安全的,而StringBuilder是非线程安全的。

源码

先看看jdk1.8中关于String、StringBuilder和StringBuffer部分的源码,我们看某个类或者某个属性是否不可变首先要看修饰类的关键字是什么,final表示不可改变也不可继承。

String

arduino 复制代码
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    // String内部使用char数组来存储数据
    private final char value[];
​
    // ...
}

源码中String类和String类的值都采用final修饰,因此String类型是不可变的。

StringBuilder

java 复制代码
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence {
    
    // 数组最大长度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
​
    // ... 其他方法
​
    // 示例方法:添加字符串
    public StringBuilder append(String str) {
        // 这里的super指向AbstractStringBuilder类
        super.append(str);
        return this;
    }
    
    // ... 其他方法继续
}
​
abstract class AbstractStringBuilder {
    char[] value;
    int count;
​
    // 实际扩展数组和添加内容在这个类中实现
    public void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > 0)
            ensureCapacityInternal(minimumCapacity);
    }
    
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
    
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }
    
    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }
​
    public AbstractStringBuilder append(String str) {
        if (str == null) {
            // ... 处理null字符串的情况
        }
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    
    // ... 其他方法
}

实际上在append的过程中调用的是抽象类AbstractStringBuilder中的append方法,从这里可以看出append方法首先根据这个字符串的长度对当前的字符数组进行扩容,可以看到StringBuilder存储字符串类型用的也是char[] value,但是这里的修饰符是缺省,因此可以对其进行扩容,即可变。

StringBuffer

StringBufferStringBuilder的差异不大,唯一的区别就是加上了关键字synchronized

java 复制代码
 public final class StringBuffer
    extends AbstractStringBuilder
    implements Serializable, CharSequence
{
    ...
    private transient char[] toStringCache;
    ...
    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    ...
}

String的"+"操作

反编译

ini 复制代码
long t1 = System.currentTimeMillis();
String str = "hollis";
for (int i = 0; i < 50000; i++) {
    String s = String.valueOf(i);
    str += s;
}
long t2 = System.currentTimeMillis();
System.out.println("+ cost:" + (t2 - t1));
ini 复制代码
long t1 = System.currentTimeMillis();
String str = "hollis";
for(int i = 0; i < 50000; i++)
{
    String s = String.valueOf(i);
    str = (new StringBuilder()).append(str).append(s).toString();
}
​
long t2 = System.currentTimeMillis();
System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());

测试demo

ini 复制代码
package org.example;
​
public class Main {
    public static void main(String[] args) {
        testStringAdd();
        testStringBuilderAdd();
    }
​
    static void testStringAdd() {
        Runtime runtime = Runtime.getRuntime();
​
        long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory();
        long startTime = System.currentTimeMillis();
​
        String result = "";
        for (int i = 0; i < 10000; i++) {
            result += "some text";
        }
​
        long endTime = System.currentTimeMillis();
        System.out.println("String concatenation with + operator took: " + (endTime - startTime) + " milliseconds");
        long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory();
        System.out.println("Memory used for String concatenation: " + (usedMemoryAfter - usedMemoryBefore) + " bytes");
    }
​
    static void testStringBuilderAdd() {
        Runtime runtime = Runtime.getRuntime();
​
        long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory();
        long startTime = System.currentTimeMillis();
​
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            builder.append("some text");
        }
​
        long endTime = System.currentTimeMillis();
        System.out.println("String concatenation with StringBuilder took: " + (endTime - startTime) + " milliseconds");
​
        long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory();
        System.out.println("Memory used for String concatenation: " + (usedMemoryAfter - usedMemoryBefore) + " bytes");
    }
}

从控制台上可以看到两者的性能差异十分明显。

vbnet 复制代码
String concatenation with + operator took: 669 milliseconds
Memory used for String concatenation: 619980944 bytes
String concatenation with StringBuilder took: 0 milliseconds
Memory used for String concatenation: 0 bytes

testStringAdd中的"+"部分反编译后,得到如下代码:

ini 复制代码
String result = "";
for (int i = 0; i < 10000; i++) {
    result = (new StringBuilder()).append("some text").toString();
}

这里可以看出来实际上"+"做的操作就是new StringBuilder()

使用场景

  • 如果字符串不需要修改,或者只是偶尔修改,使用String
  • 如果在单线程环境中需要频繁修改字符串,使用StringBuilder
  • 如果在多线程环境中需要频繁修改字符串,使用StringBuffer
相关推荐
lang201509283 分钟前
Spring Boot 入门:5分钟搭建Hello World
java·spring boot·后端
间彧1 小时前
Windows Server,如何使用WSFC+nginx实现集群故障转移
后端
间彧1 小时前
Nginx + Keepalived 实现高可用集群(Linux下)
后端
间彧1 小时前
在Kubernetes中如何部署高可用的Nginx Ingress Controller?
后端
间彧1 小时前
Ribbon负载均衡器和Nginx负载均衡器有什么区别
后端
间彧2 小时前
Nacos详解与项目实战
后端
间彧2 小时前
nginx、网关Gateway、Nacos、多个服务实例之间的数据链路详解
后端
间彧2 小时前
Nacos与Eureka在性能上有哪些具体差异?
后端
间彧2 小时前
详解Nacos健康状态监测机制
后端
间彧2 小时前
如何利用Nacos实现配置的灰度发布?
后端