简介

StringBuilder与StringBuffer是两个常用的操作字符串的类。大家都知道,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,这个类封装了StringBuilder和StringBuffer大部分操作的实现。

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

StringBuilder

AbstractStringBuilder已经实现了大部分需要的方法,StringBuilder和StringBuffer只需要调用即可。下面来看看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

StiringBuffer跟StringBuilder类似,只不过为了实现同步,很多方法使用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被修改了这个变量会被赋值为null。StringBuffer的toString如下:

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

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

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

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

总结

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

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

  • StringBuilder和StringBuffer的默认容量都是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. NOI2001 食物链

    食物链 题目描述 动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形.A 吃 B,B 吃 C,C 吃 A. 现有 N 个动物,以 1 - N 编号.每个动物都是 A,B,C 中的一种 ...

  2. *C语言有关指针的变量声明中的几个易错点

    转至:http://my.oschina.net/ypimgt/blog/108265   Technorati 标签:  指针, typedef, const, define 我们都知道,至少听说过 ...

  3. 动态库Link error 一种可能

    一般出现link error都是链接时找不到对应函数. 前些天出现这种错误,我反复地检查,都发现动态库里明确的是有对应函数的. 代码里有这样一句#pragma comment(lib,"li ...

  4. 玩转webpack(二):webpack的核心对象

    欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 作者介绍:陈柏信,腾讯前端开发,目前主要负责手Q游戏中心业务开发,以及项目相关的技术升级.架构优化等工作. 前言 webpack 是一个强大的模 ...

  5. virtualbox创建虚拟机及增加硬盘记录

    创建虚拟机 jken01VBoxManage createvm --name "jken01" --basefolder /data/virtualDir/jken01 --reg ...

  6. 安装与配置cacti 0.8.8b

    cacti安装与配置 一.安装所需要的软件 Apache    安装Apache文档 Mysql      安装Mysql文档 Php       安装PHP文档 Rrdtool    安装rrdto ...

  7. python中的各种转化

    1.数之间的转化 #浮点型to整型 >>> int(5.6) 5 #整型to浮点型 >>> float(5) 5.0 2.数与字符串 #字符串to整型 >&g ...

  8. BZOJ 1998: [Hnoi2010]Fsk物品调度 [置换群 并查集]

    传送门 流水线上有n个位置,从0到n-1依次编号,一开始0号位置空,其它的位置i上有编号为i的盒子.Lostmonkey要按照以下规则重新排列这些盒子. 规则由5个数描述,q,p,m,d,s,s表示空 ...

  9. BZOJ 3040: 最短路(road) [Dijkstra + pb_ds]

    3040: 最短路(road) Time Limit: 60 Sec  Memory Limit: 200 MBSubmit: 2476  Solved: 814[Submit][Status][Di ...

  10. iOS 应用开发,用户密码存储技术--KeyChain

    文/清雪飘香(简书作者)原文链接:http://www.jianshu.com/p/c41525172aee著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 这次的Xcode 事件,让我 ...