简介

StringBuilderStringBuffer是两个常用的操作字符串的类。大家都知道,StringBuilder是线程不安全的,而StringBuffer是线程安全的。前者是JDK1.5加入的,后者在JDK1.0就有了。下面分析一下它们的内部实现。

继承关系

public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence

public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence

可以看到,两个类的继承关系是一模一样的。Serializable是可以序列化的标志。CharSequence接口包含了charAt()length() 、subSequence()toString()这几个方法,String类也实现了这个接口。这里的重点是抽象类AbstractStringBuilder,这个类封装了StringBuilderStringBuffer大部分操作的实现。

AbstractStringBuilder

变量及构造方法

char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}
    

AbstractStringBuilder内部用一个char[]数组保存字符串,可以在构造的时候指定初始容量方法。

扩容

public void ensureCapacity(int minimumCapacity) {
    if (minimumCapacity > 0)
        ensureCapacityInternal(minimumCapacity);
}
 private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}

扩容的方法最终是由expandCapacity()实现的,在这个方法中首先把容量扩大为原来的容量加2,如果此时仍小于指定的容量,那么就把新的容量设为minimumCapacity。然后判断是否溢出,如果溢出了,把容量设为Integer.MAX_VALUE。最后把value值进行拷贝,这显然是耗时操作。

append()方法

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

append()是最常用的方法,它有很多形式的重载。上面是其中一种,用于追加字符串。如果strnull,则会调用appendNull()方法。这个方法其实是追加了'n''u''l''l'这几个字符。如果不是null,则首先扩容,然后调用StringgetChars()方法将str追加到value末尾。最后返回对象本身,所以append()可以连续调用。

StringBuilder

AbstractStringBuilder已经实现了大部分需要的方法,StringBuilderStringBuffer只需要调用即可。下面来看看StringBuilder的实现。

构造器

public StringBuilder() {
    super(16);
}
public StringBuilder(int capacity) {
    super(capacity);
}
public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
}
public StringBuilder(CharSequence seq) {
    this(seq.length() + 16);
    append(seq);
}

可以看出,StringBuilder默认的容量大小为16。当然也可以指定初始容量,或者以一个已有的字符序列给StringBuilder对象赋初始值。

append()方法

public StringBuilder append(String str) {
    super.append(str);
    return this;
}
public StringBuilder append(CharSequence s) {
    super.append(s);
    return this;
}

append()的重载方法很多,这里随便列举了两个。显然,这里是直接调用的父类AbstractStringBuilder中的方法。

toString()

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

toString()方法返回了一个新的String对象,与原来的对象不共享内存。其实AbstractStringBuilder中的subString()方法也是如此。

SringBuffer

StiringBufferStringBuilder类似,只不过为了实现同步,很多方法使用lSynchronized修饰,如下面的方法:

public synchronized int length() {
        return count;
}
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}
public synchronized void setLength(int newLength) {
    toStringCache = null;
    super.setLength(newLength);
}

可以看到,方法前面确实加了Synchronized

另外,在上面的append()以及setLength()方法里面还有个变量toStringCache。这个变量是用于最近一次toString()方法的缓存,任何时候只要StringBuffer被修改了这个变量会被赋值为nullStringBuffertoString如下:

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

在这个方法中,如果toStringCachenull则先缓存。最终返回的String对象有点不同,这个构造方法还有个参数true。找到String的源码看一下:

 String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

原来这个构造方法构造出来的String对象并没有实际复制字符串,只是把value指向了构造参数,这是为了节省复制元素的时间。不过这个构造器是具有包访问权限,一般情况下是不能调用的。

总结

  • StringBuilderStringBuffer都是可变字符串,前者线程不安全,后者线程安全。

  • StringBuilderStringBuffer的大部分方法均调用父类AbstractStringBuilder的实现。其扩容机制首先是把容量变为原来容量的2倍加2。最大容量是Integer.MAX_VALUE,也就是0x7fffffff

  • StringBuilderStringBuffer的默认容量都是16,最好预先估计好字符串的大小避免扩容带来的时间消耗。

Java StringBuilder 和 StringBuffer 源码分析的更多相关文章

  1. java线程池ThreadPoolExector源码分析

    java线程池ThreadPoolExector源码分析 今天研究了下ThreadPoolExector源码,大致上总结了以下几点跟大家分享下: 一.ThreadPoolExector几个主要变量 先 ...

  2. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

  3. 死磕 java集合之PriorityBlockingQueue源码分析

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  4. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  5. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  6. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

  7. 死磕 java集合之ConcurrentHashMap源码分析(三)

    本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...

  8. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  9. 死磕 java集合之LinkedList源码分析

    问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...

随机推荐

  1. Python算法——二叉树

    一.二叉树 from collections import deque class BiTreeNode: def __init__(self, data): self.data = data sel ...

  2. float 的不确定性

    很多时候,大家都知道,浮点型这个东西,本身存储就是一个不确定的数值,你永远无法知道,它是 0 = 0.00000000000000123 还是 0 = 0.00000000000999这样的东西.也许 ...

  3. JavaScript函数调用模式

    1.方法调用模式: var myObj = { value : 0; increment:function(inc){ this.value += typeof inc === 'number' ? ...

  4. windows下安装Python2和Python3共存

    一.Python安装 1.下载安装包 https://www.python.org/ftp/python/2.7.14/python-2.7.14.amd64.msi # 2.7安装包 https:/ ...

  5. aop难点解析。

    静态织入和动态织入的区别? 需求示例:假设有一个包,一个包当中有一个方法,我们想在这个方法的前后,加上环绕. 那么怎么加呢? 把知道的都说一遍. 1.建立JsonService 2.建立JSONASP ...

  6. ipython的用法详解

    ipython是一个升级版的交互式python命令行工具. ipython安装 pip install ipython 等到命令执行完成后显示successfully表示完装成功 在命令提示符下输入i ...

  7. 51NOD 1584 加权约数和 [莫比乌斯反演 转化 Trick]

    1584 加权约数和 题意:求\(\sum_{i=1}^{N} \sum_{j=1}^{N} {\max(i,j)\cdot \sigma(i\cdot j)}\) 多组数据\(n \le 10^6, ...

  8. smallcorgi/Faster-RCNN_TF训练自己的数据

    熟悉了github项目提供的训练测试后,可以来训练自己的数据了.本文只介绍改动最少的方法,只训练2个类, 即自己添加的类(如person)和 background,使用的数据格式为pascal_voc ...

  9. mac攻略(3) -- brew使用

    [http://www.cnblogs.com/redirect/p/6131642.html] 1.介绍 brew是一个软件包管理工具,类似于centos下的yum或者ubuntu下的apt-get ...

  10. Docker可视化管理工具Shipyard安装与配置

    Shipyard简介 Shipyard是一个集成管理docker容器.镜像.Registries的系统,它具有以下特点: 1.支持多节点的集成管理 2.可动态加载节点 3.可托管node下的容器 镜像 ...