对于 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?

为什么 StringBuffer 有 toStringCache 而 StringBuilder 没有?的更多相关文章

  1. 证明StringBuffer线程安全,StringBuilder线程不安全

    证明StringBuffer线程安全,StringBuilder线程不安全证明StringBuffer线程安全StringBuilder线程不安全测试思想测试代码结果源码分析测试思想分别用1000个线 ...

  2. java中StringBuffer与String、StringBuilder的区别

    在java中我们经常可以看到StringBuffer和String的用法,但是我自己在使用过程中,经常会将两者弄混淆,今天我们就来了解一下两者的区别: 我们首先来看一下我们的官方API中的简单介绍: ...

  3. [常用类]StringBuffer 类,以及 StringBuilder 类

    线程安全,可变的字符序列. 字符串缓冲区就像一个String ,但可以修改. 在任何时间点,它包含一些特定的字符序列,但可以通过某些方法调用来更改序列的长度和内容. 字符串缓冲区可以安全地被多个线程使 ...

  4. java‘小秘密’系列(一)---String、StringBuffer、StringBuilder

    java'小秘密'系列(一)---String.StringBuffer.StringBuilder 前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现b ...

  5. 从源码角度简单看StringBuilder和StringBuffer的异同

    概述 StringBuilder和StringBuffer是两个容易混淆的概念,本文从源码入手,简单看二者的异同. 容易知道的是,这两者有一个是线程安全的,而且线程安全的那个效率低. java doc ...

  6. [十四]基础类型之StringBuffer 与 StringBuilder对比

    StringBuilder 和 StringBuffer是高度类似的两个类 StringBuilder是StringBuffer的版本改写,下面从几个方面简单的对比下他们的区别 类继承关系 上文中,我 ...

  7. StringBuffer 和 StringBuilder 的 3 个区别

    StringBuffer 和 StringBuilder 它们都是可变的字符串,不过它们之间的区别是 Java 初中级面试出现几率十分高的一道题.这么简单的一道题,栈长在最近的面试过程中,却经常遇到很 ...

  8. 字符串之StringBuffer 与 StringBuilder的对比

    StringBuilder 和 StringBuffer是高度类似的两个类 StringBuilder是StringBuffer的版本改写,下面从几个方面简单的对比下他们的区别 原文地址:[十四]基础 ...

  9. java基础解析系列(一)---String、StringBuffer、StringBuilder

    java基础解析系列(一)---String.StringBuffer.StringBuilder 前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bu ...

随机推荐

  1. react+redux项目搭建及示例

    React + Redux示例,实现商品增删改 目录结构 1.项目搭建 1.1 使用create-react-app react_redux创建项目 1.2 安装使用redux需要的依赖 npm in ...

  2. setTimeout和setInterval的区别,包含内存方面的分析?

    setTimeout表示间隔一段时间之后执行一次调用,而setInterval则是每间隔一段时间循环调用,直至clearInterval结束. 内存方面,setTimeout只需要进入一次队列,不会造 ...

  3. 【学习笔记】使用 bitset 求解较高维偏序问题

    求解五维偏序 给定 \(n(\le 3\times 10^4)\) 个五元组,对于每个五元组 \((a_i, b_i, c_i, d_i, e_i)\),求存在多少个 \(1\le j\le n\) ...

  4. 《图解TCP/IP》笔记

    OSI参考模型 协议分层 为什么需要分层? 简化网络协议. 每一层只需要衔接上下层的服务. 利于模块化开发. 解耦. 分层的问题 过分模块化.提高数据处理的开销. OSI参考模型 作用及意义 将复杂的 ...

  5. idea 中使用Mybatis Generator逆向工程生成代码

    通过MAVEN完成 Mybatis 逆向工程 1. POM文件中添加插件 在 pom 文件的build 标签中 添加 plugin 插件和 数据库连接 jdbc 的依赖. <build> ...

  6. selenium 浏览器的操作

    一.浏览器的最大化 #coding=utf-8 from selneium import wbedriver driver = wbedriver.Firefox() #将webdriver 的Fir ...

  7. Spark-2-性能监控方式

    1 Spark Web UI Spark提供了一些基本的Web监控页面,对于日常监控十分有用. 通过http://master:4040(默认端口是4040,可以通过spark.ui.port修改)我 ...

  8. 属于同一网段的ip是不是就在同一个局域网?

    参考文章链接: https://zhidao.baidu.com/question/350887200.html?qbl=relate_question_0&word=%D4%DA%D2%BB ...

  9. 去掉关闭按钮同时禁用alt+f4进行关闭

    public int ctype = 0; private const int GWL_STYLE = -16; private const int WS_SYSMENU = 0x80000; [Sy ...

  10. JavaEE在职加薪课好客租房项目实战视频教程

    JavaEE在职加薪课好客租房项目实战视频教程课程介绍:       本课程采用SOA架构思想进行设计,基于目前主流后端技术框架SpringBoot.SpringMVC.Mybaits.Dubbo等来 ...