• 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 对象,因此效率很差。

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

    测试如下:

            // 获取开始时间
            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 修饰类,也没有子类去改变不可变的状态。

  • 相关阅读:
    线程同步(解决线程安全问题)
    【Leetcode每日一题】 动态规划 - 简单多状态 dp 问题 - 删除并获得点数(难度⭐⭐)(76)
    【Godot4自学手册】第四十一节背包系统(一)UI设置
    算法学习笔记(20): AC自动机
    加权平均的重要作用
    汽车零部件制造迎来智能化升级,3D视觉定位系统助力无人化生产线建设
    K8S篇之k8s常用操作指令
    【SSM】Mybatis01
    Codeforces Round #822 (Div. 2) C. Removing Smallest Multiples
    【10. 差分】
  • 原文地址:https://blog.csdn.net/qq_32046111/article/details/125490924