# Java 中 String、StringBuffer、StringBuilder 的用法与区别

# 一、区别

# 1.1 可变性

String 是不可变的。

String s = new String("Hello");
System.out.println("s = " + s);	// output: s = Hello
// 转为小写
s = s.toLowerCase();
System.out.println("s = " + s); // output: s = hello
1
2
3
4
5

咦,你不是说 String 是不可变的吗?这怎么就变了。s 变为小写字母了。

String 不可变指的是 String 指向的对象不可变,而不是 String 对象引用不可变。

s 是一个对象引用,存放 Hello的地址,s 存在栈中。

Hello是字符串对象,它的值不会发生改变,存在堆中。

Hello 字符串没有被改变,只是新建了一个新的 hello 字符串,然后 s这个引用指向了新的。

Hello 字符串后面会被垃圾回收处理。

# 1.2 线程安全性

  • String 不可变,线程安全。
  • StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
  • StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

# 1.3 性能

每次对 String 对象进行改变的时候,都会生成一个新的 String 对象,然后将引用指向新的 String 对象,然后丢弃掉旧的 String 对象,因此效率很差。

StringBuilder 会预分配缓冲区,每次只会对 StringBuilder 对象本身进行操作,而不是生成新的对象并改变对象引用。因此效率比较高。

测试如下:

        // 获取开始时间
        long startTime = System.currentTimeMillis();
        String s = "";
        for (int i = 0; i < 1000; i++) {
            s = s + "," + i;
        }
        System.out.println(s);
        // 获取结束时间
        long endTime = System.currentTimeMillis();
        // output: time consuming: 11ms
        System.out.println("time consuming: "+ (endTime - startTime) + "ms");
1
2
3
4
5
6
7
8
9
10
11
        // 获取开始时间
        long startTime = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder(1024);
        for (int i = 0; i < 1000; i++) {
            sb.append(',');
            sb.append(i);
        }
        String s = sb.toString();
        System.out.println(s);
        // 获取结束时间
        long endTime = System.currentTimeMillis();
        // output: time consuming: 1ms
        System.out.println("time consuming: "+ (endTime - startTime) + "ms");
1
2
3
4
5
6
7
8
9
10
11
12
13

一个 11 ms,一个 1 ms,效率孰高孰低,一眼便知。

# 二、String 不可变的原因

# 2.1 String 源码分析

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {
    @Stable
    private final byte[] value;     
}
1
2
3
4
5
6

final 修饰 String 类,表明 String 类不能被继承,因此没有子类。

final 修饰字段:

  • 字段是基本类型,那么字段不可变
  • 字段是引用类型,那么引用的对象不可变

因此很多人就认为 final 修饰了 value 就是不可变的原因,错错错,大错特错。

byte[]是个数组类型,数组类型是引用类型,只能说明 value 不能指向其他对象, value 本身还是可以修改的。

final byte[] value = {1, 2, 3};
byte[] temp = {4, 5, 6};
value = temp;
// error: java: 无法为最终变量value分配值
1
2
3
4
final byte[] value = {1, 2, 3};
value[0] = 4;
for(byte b: value) {
    System.out.println(b);
}
// output: 4 2 3
1
2
3
4
5
6

其实不可变的原因是:String 类中的各个方法都没有去改变 String 对象的值,都是去 new String()产生一个新的对象出来。然后 final 修饰类,也没有子类去改变不可变的状态。