zoukankan      html  css  js  c++  java
  • StringBuffer&StringBuilder区别详解

    序言

    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已经实现了一次,不知何为!

  • 相关阅读:
    利用DTrace实时检测MySQl
    改进MySQL Order By Rand()的低效率
    RDS for MySQL查询缓存 (Query Cache) 的设置和使用
    RDS For MySQL 字符集相关说明
    RDS for MySQL 通过 mysqlbinlog 查看 binlog 乱码
    RDS for MySQL Mysqldump 常见问题和处理
    RDS for MySQL Online DDL 使用
    RDS MySQL 表上 Metadata lock 的产生和处理
    RDS for MySQL 如何使用 Percona Toolkit
    北京已成为投融资诈骗重灾区:存好骗子公司黑名单,谨防上当!
  • 原文地址:https://www.cnblogs.com/interdrp/p/5972309.html
Copyright © 2011-2022 走看看