序言

StringBuffer与StringBuilder是java.lang包下被大家熟知的两个类。其异同为:一、长度都是可扩充的;二、StringBuffer是线程安全的,StringBuilder是线程不安全的。那么他们的长度是如何实现动态扩充以及StringBuffer的线程安全是如何实现的呢?通过“深度”阅读它们的源代码,最终弄明白其中的缘由。

正文

首先上一张StringBuffer和StringBuilder类结构图:

抽象类AbstractStringBuilder(也是核心实现类)实现了Appendable和CharSequence两个接口;StringBuffer与StringBuilder统统继承自AbstractStringBuilder,并且实现了java.io.Serializable和CharSequence接口。

下面简单描述下这几个接口所起到的作用(引用自中文api)。

    • Appendable
    • :能够被添加 char 序列和值的对象。如果某个类的实例打算接收java.util.

Formatter

    的格式化输出,那么该类必须实现 Appendable 接口
      。要添加的字符应该是有效的 Unicode 字符,正如

Unicode Character Representation

    中描述的那样。注意,增补字符可能由多个 16 位char 值组成
  • CharSequence:CharSequence 是 char 值的一个可读序列。此接口对许多不同种类的 char 序列提供统一的只读访问。char 值表示 Basic Multilingual Plane (BMP) 或代理项中的一个字符。有关详细信息,请参阅 Unicode 字符表示形式。此接口不修改 equals 和 hashCode 方法的常规协定。因此,通常未定义比较实现 CharSequence 的两个对象的结果。每个对象都可以通过一个不同的类实现,而且不能保证每个类能够测试其实例与其他类的实例的相等性。因此,使用任意 CharSequence 实例作为集合中的元素或映射中的键是不合适的。
  • Serializable:类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
  • AbstractStringBuilde
    r这个抽象类提供了StringBuffer和StringBuilder绝大部分的实现。在AbstractStringBuilder的描述中说:如果去掉线程安全,那么StringBuffer和StringBuilder是完全一致的。从实现的角度来说,StringBuffer所有方法(构造方法除外,因为没有必要)签名中都使用synchronized限定,也就是所有的方法都是同步的。

eg.StringBuffer中replace()方法

1
2
3
4
public synchronized StringBuffer replace(int start, int end, String str) {
        super.replace(start, end, str);
        return this;
}

StringBuilder中replace():

1
2
3
4
public StringBuilder replace(int start, int end, String str) {
        super.replace(start, end, str);
        return this;
}

区别仅仅在方法签名上是否有synchronized。

另外需要稍稍注意的问题是:StringBuffer同步只同步目标,比如:sb.append("i am not synchronized"),sb是同步的,而其中的参数未必是同步的。

而它们两个可扩展长度则是通过ensureCapacity(int minimumCapacity)来验证当前长度是否小于参数minimumCapacity,如果成立则进行分配空间。分配新空间的步长为(当前长度+1)的两倍。

实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > value.length) {
            expandCapacity(minimumCapacity);
        }
}
void expandCapacity(int minimumCapacity) {
        int newCapacity = (value.length + 1) * 2;
        if (newCapacity < 0) {
            newCapacity = Integer.MAX_VALUE;
        } else if (minimumCapacity > newCapacity) {
            newCapacity = minimumCapacity;
        }
        value = Arrays.copyOf(value, newCapacity);
}

如果新的长度小于0(溢出了),则使用Integer的最大值作为长度。

另外,在阅读源码的过程中,发现两个有趣的问题,下面一一道来。

第一个,就是reverse的实现。其实我是第一次看StringBuilder和StringBuffer的源码,这里面的reverse的实现是我所知道的java中的最高效的实现,没有之一。

上源码,再做解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public AbstractStringBuilder reverse() {
        boolean hasSurrogate = false;
        int n = count - 1;
        for (int j = (n-1) >> 1; j >= 0; --j) {
            char temp = value[j];
            char temp2 = value[n - j];
            if (!hasSurrogate) {
                hasSurrogate = (temp >= Character.MIN_SURROGATE && temp <= Character.MAX_SURROGATE)
                    || (temp2 >= Character.MIN_SURROGATE && temp2 <= Character.MAX_SURROGATE);
            }
            value[j] = temp2;
            value[n - j] = temp;
        }
        if (hasSurrogate) {
            // Reverse back all valid surrogate pairs
            for (int i = 0; i < count - 1; i++) {
                char c2 = value[i];
                if (Character.isLowSurrogate(c2)) {
                    char c1 = value[i + 1];
                    if (Character.isHighSurrogate(c1)) {
                        value[i++] = c1;
                        value[i] = c2;
                    }
                }
            }
        }
        return this;
}

reverse分成两个部分:前面一个循环与后面的判断。

首先地一个循环很高效,循环次数为长度(count)的一半,而且使用>>位移运算,交换数组value[j]与value[n-j]的值。这里一是循环次数少,而是使用最高效的位移运算所以说这个reverse很高效。在反转过程中还完成了一件事:就是为hasSurrogate赋值。赋值的依据就是value[j]与value[n-j]两个字符时候有一个在\uD800和\uDFFF之间,如果有则赋值为true。

而hasSurrogate的值作为下面一个if分支的依据,如果为true,则从头到尾循环一遍。至于为何要判断hasSurrogate,以及下面一个循环的意义,其实到这里应该已经结束了,在我整理StringBuffer和StringBuilder结构图时发现(“刨祖坟”行家啊),发现它们两个又再次实现了CharSequence接口,为何说再次呢,因为AbstractStringBuilder已经实现了一次,不知何为!

StringBuffer&StringBuilder区别详解的更多相关文章

  1. 基于Java的打包jar、war、ear包的作用与区别详解

      本篇文章,小编为大家介绍,基于Java的打包jar.war.ear包的作用与区别详解.需要的朋友参考下   以最终客户的角度来看,JAR文件就是一种封装,他们不需要知道jar文件中有多少个.cla ...

  2. Android中Intent传值与Bundle传值的区别详解

    Android中Intent传值与Bundle传值的区别详解 举个例子我现在要从A界面跳转到B界面或者C界面   这样的话 我就需要写2个Intent如果你还要涉及的传值的话 你的Intent就要写两 ...

  3. php 去除html标记--strip_tags与htmlspecialchars的区别详解

    php 去除html标记--strip_tags与htmlspecialchars的区别详解 作者: 字体:[增加 减小] 类型:转载 时间:2013-06-26   本篇文章是对php中去除html ...

  4. HTTP POST GET 本质区别详解

    HTTP POST GET 本质区别详解 一 原理区别 一般在浏览器中输入网址访问资源都是通过GET方式:在FORM提交中,可以通过Method指定提交方式为GET或者POST,默认为GET提交 Ht ...

  5. javascript中=、==、===区别详解

    javascript中=.==.===区别详解今天在项目开发过中发现在一个小问题.在判断n==""结果当n=0时 n==""结果也返回了true.虽然是个小问题 ...

  6. [转]ESCAPE()、ENCODEURI()、ENCODEURICOMPONENT()区别详解

    escape().encodeURI().encodeURIComponent()区别详解 JavaScript中有三个可以对字符串编码的函数,分别是: escape,encodeURI,encode ...

  7. phpcms加载系统类与加载应用类之区别详解

    <?php 1. 加载系统类方法load_sys_class($classname, $path = ''", $initialize = 1)系统类文件所在的文件路径:/phpcms ...

  8. Bind和Eval的区别详解

    原文:Bind和Eval的区别详解 1.简单描述Eval和Bind的区别 绑定表达式 <%# Eval("字段名") %> <%# Bind("字段名& ...

  9. 转-HTTP POST GET SOAP本质区别详解

    原文链接:HTTP POST GET SOAP本质区别详解 一 原理区别 一般在浏览器中输入网址访问资源都是通过GET方式:在FORM提交中,可以通过Method指定提交方式为GET或者POST,默认 ...

随机推荐

  1. pycharm Cannot find declaration to goto----解决方案

    系统中已经有了该类库,还是找不到类提示 close the project in intellij. close intellij. go to the project folder and dele ...

  2. java项目@override报错问题

    有时候Java的Eclipse工程换一台电脑后编译总是@override报错,把@override去掉就好了,但不能从根本上解决问题,因为有时候有@override的地方超级多. 这是jdk的问题,@ ...

  3. js注意

    使用集成函数注意返回值,有的不会改变现有对象,仅返回对象的副本,而有的会改变现有对象并返回该对象. 变量名不能和函数名相同,否则会被覆盖. 查询时看清楚返回的是单个元素还是集合,如果是使用返回集合的方 ...

  4. JS延时提示框

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 31.0px Consolas; color: #2b7ec3 } p.p2 { margin: 0.0px ...

  5. RecyclerView+CardView简单使用

    RecyclerView取代Listview用来显示数据,除此之外还能实现瀑布流的布局.CardView让我们的界面更好看,此外还将使用官方的下拉刷新. 添加支持: compile 'com.andr ...

  6. javascript 中的console.log和弹出窗口alert

    主要是方便你调式javascript用的.你可以看到你在页面中输出的内容. 相比alert他的优点是: 他能看到结构话的东西,如果是alert,淡出一个对象就是[object object],但是co ...

  7. 关于QT的系统总结

    编译环境与开发流程 开发QT有两种IDE可以使用,一种是使用 VS + Qt 的插件,另一种就是使用QtCreator工具.前一种是微软的工具,用的都比较多容易上手,缺点是信号槽的支持不太好,需要手写 ...

  8. 解决linux下oracle-11g打不开dbca问题

    linux下oracle安装完毕后,出现建立数据库命令dbca无法使用问题,如图: 解决办法: 在32位的linux环境下,安装32位oracle11g会有这个bug,主要装个补丁(patch nam ...

  9. ASP.NET MVC学习之模型模板篇

    一.前言 如果你使用ASP.NET MVC制作后台一定会爱上它的EditorForModal.DisplayForModal和LabelForModal方法,因为这些方法可以将模型直接变成对应的标签, ...

  10. [Java Web] 4、JavaScript 简单例子(高手略过)

    内容概览: JavaScript简介 JavaScript的基本语法 JavaScript的基本应用 JavaScript的事件处理 window对象的使用 JavaScript简介: JavaScr ...