• 为什么 StringBuffer 有 toStringCache 而 StringBuilder 没有?


    对于 StringBuilder 和 StringBuffer 的源码会发现,StringBuffer 中有一个叫 toStringCache 的成员变量,用来缓存 toString() 方法返回字符串对应的字符数组,而在 StringBuilder 里面却没有。

    先看一下代码。

    StringBuffer 中的 toString() :

        @Override
        public synchronized String toString() {
            if (toStringCache == null) {
                toStringCache = Arrays.copyOfRange(value, 0, count);
            }
            return new String(toStringCache, true);
        }
    

    StringBuilder 中的 toString() :

        @Override
        public String toString() {
            // Create a copy, don't share the array
            return new String(value, 0, count);
        }
    

    StringBuffer 调用了 String(char[] value, boolean share) 来构造一个 String 对象,它传入了一个字符数组,也就是缓存 toStringCache。Java 中定义,一个 String 对象是常量,是不能被改变的,因此 StringBuffer 的 toString() 返回的对象也应该是不能被改变。也就意味着传入的 toStringCache 数组的元素的值也不能被改变,否则由它构造的 String 对象就会改变。

    如下,我们通过反射调用 String(char[] value, boolean share) 构造处一个字符串对象,然后修改 value 数组的值,会发现字符串对象发生了改变。

    class ToStringCacheTest {
    
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            Constructor<String> constructor = String.class.getDeclaredConstructor(char[].class, boolean.class);
            constructor.setAccessible(true);
    
            char[] value = new char[]{'R', 'o', 'b', 'o', 't', 'h', 'y'};
    
            String str = constructor.newInstance(value, true);
    
            System.out.println(str); // 输出:Robothy
            value[0] = 'A';          // 修改 str 绑定的字符数组 value
            System.out.println(str); // 输出:Aobothy
        }
    
    }
    

    StringBuffer 中的 toStringCache 是字符数组 value 复制的一个副本,每当 value 发生改变时,toStringCache 都会被置为空。这就保证了每次只要 StringBuffer 对象发生改变,再调用 toString() 方法就必然产生一个新的 toStringCache 数组,从而保证了引用了旧的 toStringCache 的字符串对象不会发生改变。即使多个线程同时访问 StringBuffer 对象,某一时刻也只有一个线程能够进入修改 toStringCache 和 value 的代码块,这通过修饰 StringBuffer 方法的 synchroinzed 关键字来保证。

    如 StringBuffer 中的 append(String str) 方法:

        @Override
        public synchronized StringBuffer append(String str) {
            toStringCache = null;
            super.append(str);
            return this;
        }
    

    而假设 StringBuilder 为了提高效率,专门设计为单线程环境使用,方法没有 synchronized 修饰。如果也和 StringBuffer 一样这么做,非常容易出现不一致的情况,使得已经产生的 String 对象发生改变。

    那么 StringBuffer 中 toStringCache 存在的必要性如何?它调用的是下面这个构造方法来创建 String 对象,构造 String 对象时直接共享传入的字符数组 value,而不是像 public String(char value[]) 一样复制一份。

        /*
        * Package private constructor which shares value array for speed.
        * this constructor is always expected to be called with share==true.
        * a separate constructor is needed because we already have a public
        * String(char[]) constructor that makes a copy of the given char[].
        */
        String(char[] value, boolean share) {
            // assert share : "unshared not supported";
            this.value = value;
        }
    

    从源码中可以知道,StringBuffer 中使用 toStringCache 通过共享一个字符数组,提供构造 String 的速度,这是一个好处。另一个好处是连续多次调用 toString() 方法是不会产生多个内容相同的 String 对象。

    但是,这些好处仅仅是在多次调用 toString() 方法且 StringBuffer 对象没有发生改变时才能体现。而实际编写代码的过程中,很少会在没有修改 StringBuffer 的情况下重复调用 toString() 方法,所以它并没有太大的实际作用。

    之所以它存在 JDK 源码里,有人解释是由于历史代码遗留的原因,现在不修改是因为它没有什么坏处,修改了反而需要重新测试代码。

    参考

    stackoverflow.com, Why StringBuffer has a toStringCache while StringBuilder not?

  • 相关阅读:
    Mapbox GL JS使用小结(一)
    js 跳转链接的几种方式
    使用iis 部署 .net项目遇到的问题
    ROS 导入示例程序并建立工程运行
    C# WPF程序增加终端串口打印调试信息
    C# 继承方法重写调用测试
    C# 迭代器实现
    C# 引用和值都按照引用传递(其实传递的就是指针)
    C# string引用类型参数不变性
    C# 值类型和引用类型
  • 原文地址:https://www.cnblogs.com/robothy/p/13895241.html
Copyright © 2020-2023  润新知