简介

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. 八大排序算法Java实现

    本文对常见的排序算法进行了总结. 常见排序算法如下: 直接插入排序 希尔排序 简单选择排序 堆排序 冒泡排序 快速排序 归并排序 基数排序 它们都属于内部排序,也就是只考虑数据量较小仅需要使用内存的排 ...

  2. Java中的最值

    Double.MAX_VALUE     等于 (2-2^(-52)) * 2^1023 约等于2^1024Double.MIN_VALUE      最小正非零值 2^(-1074)Long.MAX ...

  3. 包装类和基本类型区别?(integer和int取值范围一样大)

    1.声明方式不同,int不需要new .Integer需要new 2.性质上根本不同点:int是基本数据类型.Integer是引用数据类型,它有自己的属性,方法 3.存储位置和方式不同:int是存储在 ...

  4. 浏览器中页面的visibility状态及变化监听

    需求 在浏览器中播放视频,当用户进行页面切换操作时.需要根据视频播放页是否处于可见状态,来控制视频的暂停及重新播放. 相关文档 参考MDN中,关于页面的可见性相关的API说明.https://deve ...

  5. 怎么改变title属性的样式?

    我们经常会设置title属性来显示提示的内容,最常见的一种就是超过文本框的内容显示省略号,鼠标移上去显示完整的内容,这里顺便说下显示省略号的设置,如 div{text-overflow:ellipsi ...

  6. Mysql基础安装,初视篇

    mysql 跟所有的数据库软件一样分为  服务端和客户端: 下载:在官网里面选择 download 社区版本,mysql,社区版本 安装: win环境下: 第一步:解压文件出来 第二步:在bin文件下 ...

  7. java对象序列化、反序列化

    平时我们在Java内存中的对象,是无法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即存储对象中的状态.一 ...

  8. Spring整合JMS(三)——MessageConverter介绍

    原文链接:http://haohaoxuexi.iteye.com/blog/1900937 1.4     消息转换器MessageConverter MessageConverter的作用主要有两 ...

  9. getopt for windows

    Glibc库里有个getopt用于解析命令行参数,挺方便的,下面的是别人从Glibc源码的获取的几个getopt相关的文件,已经将平台相关的修改掉,windows下可以调用,本来是要用没用到就没去看正 ...

  10. C#常用代码片段备忘

    以下是从visual studio中整理出来的常用代码片段,以作备忘 快捷键: eh 用途: 类中事件实现函数模板 private void MyMethod(object sender, Event ...