对于 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. python菜鸟教程学习10:数据结构

    列表方法 list.append(x):把一个元素添加到列表的结尾,相当于 a[len(a):] = [x]. list.extend(L):通过添加指定列表的所有元素来扩充列表,相当于 a[len( ...

  2. SpringCloud-服务间通信方式

    接下来在整个微服务架构中,我们比较关心的就是服务间的服务改如何调用,有哪些调用方式? 总结:在springcloud中服务间调用方式主要是使用 http restful方式进行服务间调用 1. 基于R ...

  3. Scrum 冲刺 第二篇

    Scrum 冲刺 第二篇 每日会议照片 昨天已完成工作 队员 昨日完成任务 黄梓浩 初步完成app项目架构搭建 黄清山 完成部分个人界面模块数据库的接口 邓富荣 完成部分后台首页模块数据库的接口 钟俊 ...

  4. 转:解释lsh

    Locality sensitive hashing - LSH explained The problem of finding duplicate documents in a list may ...

  5. 基于Fisco-Bcos的区块链智能合约-业务数据上链SDK实现

    合约的编写 基于springboot : https://github.com/FISCO-BCOS/spring-boot-starter pragma solidity ^0.4.24; cont ...

  6. python协程需要注意的

    python协程需要注意的点 都在注释里 # -*- coding: utf-8 -*- import asyncio import time from geeker import schedule ...

  7. K8s 终将废弃 docker,TKE 早已支持 containerd

    近日 K8s 官方称最早将在 1.23版本弃用 docker 作为容器运行时,并在博客中强调可以使用如 containerd 等 CRI 运行时来代替 docker.本文会做详细解读,并介绍 dock ...

  8. Error while instantiating 'org.apache.spark.sql.hive.HiveSessionStateBuilder': —— windows 开发环境使用spark 无法访问hdfs 问题解决

    ## 错误: ## 解决方案: 下载 hadoop 的可执行tar包,解压放在windows 本地,并配置环境变量. 在 解压后的文件夹的bin目录下放入两个文件: winutils.exe, had ...

  9. 【英雄帖】FreeRedis 邀请您一起优化项目。

    嘿!各位!自 FreeRedis 开库以来,相继出现了很多贡献者,我们正在对 FreeRedis 的各功能模块做优化,这并不意味着现版的 FreeRedis 有问题,我们只是希望在某些方面做得更好.如 ...

  10. Python的环境是如何安装的,我来教你

    01 初见Python Python编程语言是荷兰人Guido van Rossum在1990年代开发出来的. Gudio拥数学和计算机双硕士学位,但他更喜欢计算机.当时Gudio觉得现有的编程语言无 ...