【源码】StringBuilder和StringBuffer源码深度剖析

//------------------------------------------------------------------------

写篇博客不容易,请尊重作者劳动成果。转载请注明出处:http://blog.csdn.net/chdjj

//------------------------------------------------------------------------

我觉得要通过源码研究一个类,应该先从整体上了解这个类,比如说这个类的继承体系,有哪些超类,继承了那些接口,提供了什么样的方法。然后,我们再去源码中了解具体的实现过程,这样才不会乱~

注:以下源码基于jdk1.7.0_11

我们来看看StringBuilder和StringBuffer的继承关系:

public final class StringBuilder//1.5开始
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

public final class StringBuffer//1.0就有了
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence  

惊人的一致,两个类都继承了AbstractStringBuilder,并且实现CharSequence和Serializable接口,Serializable接口大家都熟悉,是一个序列化的标志。那么我们先来看CharSquence接口:

package java.lang;

/**
 * @author Mike McCloskey
 * @since 1.4
 * @spec JSR-51
 */
public interface CharSequence {
    int length();
    char charAt(int index);
    CharSequence subSequence(int start, int end);
    public String toString();
}

只有四个方法,这个接口为不同的字符序列类提供了统一的规范,看到这个类有个toString的方法,说明它强制子类去实现toString,而不使用默认的toString。其他三个方法也比较好理解,甚至大家在String中还经常使用这些方法,这里就不提了。

分析完了Charsequence接口,下面再看看AbstractStringBuilder类吧,听名字就知道是个抽象类。这个类是到1.5才有的,很显然是由于增加了StringBuilder,设计者觉着可以将StringBuilder和StringBuffer进行泛化,抽取共同部分,这体现了面向对象的设计理念。

abstract class AbstractStringBuilder implements Appendable, CharSequence

AbstractStringBuilder又实现了CharSequence和Appendable接口,Appendable看名字就知道肯定跟StringBuilder和StringBuffer的可变性有关,我们果断看看Appendable接口:

package java.lang;
import java.io.IOException;
public interface Appendable {
    Appendable append(CharSequence csq) throws IOException;
    Appendable append(CharSequence csq, int start, int end) throws IOException;
    Appendable append(char c) throws IOException;
}

果然,这个接口提供了append方法的不同重载形式,返回值均为自己(注:看到这里,我想到了一句话,抽象类是对类进行抽象,而接口是对行为进行抽象,想想确实如此呢),既然AbstractStringBuilder实现了此接口,必然提供了实现,下面我们回到AbstractStringBuilder类中看看这个类都干了啥。

先看成员变量:

/**
     * The value is used for character storage.
     */
    char[] value;//用于存放字符的数组
    /**
     * The count is the number of characters used.
     */
    int count;//当前字符总数

注意,这里的value数组并没有声明为final(String类的value数组是final的),说明此数组可改变。

再看构造器:

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

一个无参构造器,一个构造器提供char数组初始容量,value数组根据此容量创建对象。

接下来看几个重要的方法。

首先是扩容的方法:

public void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > 0)
            ensureCapacityInternal(minimumCapacity);
    }
    /**
     * This method has the same contract as ensureCapacity, but is
     * never synchronized.
     */
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }
    /**
     * This implements the expansion semantics of ensureCapacity with no
     * size check or synchronization.
     */
    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);
    }

ensureCapacity方法确保当前字符数组的容量最小为minimumCapacity,首先判断这个参数是否为负,若是,则返回,否则调用ensureCapacityInternal方法,这个方法内部将判断当前容量是否小于minimumCapacity,若是,则进行扩容,否则返回。扩容是通过调用expandCapacity方法来实现的,这个方法内部实现逻辑是这样的:首先试着将当前数组容量扩充为原数组容量的2倍加上2,如果这个新容量仍然小于最小值(minimumCapacity),那么就将新容量定为(minimumCapacity),最后判断是否溢出,若溢出,则将容量定为整型的最大值0x7fffffff。容量定好之后,进行一次数组拷贝。

通过这一系列步骤,应该对我们有所启发,在创建StringBuilder或StringBuffer时,尽量估算下串的长度,给一个合理的初始值,避免多次扩容带来的效率问题。

接下来看append方法(重载太多,这里只列举1个):

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

上面这个append方法接受一个String类型的参数,也是我们最常使用的重载形式.内部逻辑比较简单,首先进行参数判断(我们写代码的时候也应该时刻注意代码的鲁棒性,注意参数的取值),若参数为空(null),则将null这几个字符作为参数,接下来判断是否需要扩容,完了之后进行一次复制,最后更新count。

还有个方法叫trimToSize,这个方法可以减少内存空间的使用,内部会进行数组复制,释放那些尚未使用的空间:

public void trimToSize() {
        if (count < value.length) {
            value = Arrays.copyOf(value, count);
        }
    }

至此,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,这个很关键哦,大家应该能够记住,如果能在面试中说出来,那也是极好的~

构造器除了有默认容量,也可以手动设置容量,为避免扩容,强烈建议大家估摸下串的大致长度~

接下来是append方法,我们推测应该调用的是AbstractStringBuilder中的append:

 public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

事实上也正是这样。

再来看下这个toString方法:

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

注意哦,这里返回的是一个新的字符串对象!

最后看下readObject和writeObject这两个方法,这两个方法都是私有的,方法的作用应该跟序列化有关。

  private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        s.defaultWriteObject();
        s.writeInt(count);
        s.writeObject(value);
    }
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        count = s.readInt();
        value = (char[]) s.readObject();
    }

到这,StringBuilder分析完毕,可以看出,StringBuilder中并没有什么复杂的逻辑,实现代码主要在AbstractStringBuilder中。

下面该分析StringBuffer了,大家都知道,StringBuffer和StringBuilder的最大区别就是StringBuffer线程安全,那么可想而知,StringBuffer的方法应该加锁了,带着这个猜想,打开StringBuffer源码:

构造器就略过了,因为StringBuffer跟StringBuilder完全一样,默认容量也是16.

下面随便来几个方法:

public synchronized int length() {
        return count;
    }
public synchronized void trimToSize() {
        super.trimToSize();
    }
 public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }

看,都加锁了吧。

你可能看到这个方法没加锁:

public StringBuffer append(CharSequence s) {
        // Note, synchronization achieved via other invocations
        if (s == null)
            s = "null";
        if (s instanceof String)
            return this.append((String)s);
        if (s instanceof StringBuffer)
            return this.append((StringBuffer)s);
        return this.append(s, 0, s.length());
    }

恩,确实没加,但注意到注释没,设计者说了,加锁的操作是通过它内部调用的其它方法实现的,所以这里没必要再进行一次加锁(锁需要浪费资源)。

剩下的代码都没啥说的了,除了加了synchronized关键字修饰,跟StringBuilder是一样一样的。

分析到此完毕,下面做个总结:

1.StringBuilder是jdk1.5引进的,而StringBuffer在1.0就有了;

2.StringBuilder和StringBuffer都是可变的字符串,可以通过append或者insert等方法修改串的内容;

3.StringBuffer是线程安全的而StringBuilder不是,因而在多线程的环境下优先使用StringBuffer,而其他情况下推荐使用StringBuilder,因为它更快;

4.StringBuilder和StringBuffer都继承自AbstractStringBuilder类,AbStractStringBuilder主要实现了扩容、append、insert方法,StrngBuilder和StringBuffer的相关方法都直接调用的父类。

5.StringBuilder和StringBuffer的初始容量都是16,程序员尽量手动设置初始值,以避免多次扩容所带来的性能问题;

6.StringBuilder和StringBuffer的扩容机制是这样的:首先试着将当前数组容量扩充为原数组容量的2倍加上2,如果这个新容量仍然小于预定的最小值(minimumCapacity),那么就将新容量定为(minimumCapacity),最后判断是否溢出,若溢出,则将容量定为整型的最大值0x7fffffff。

【源码】StringBuilder和StringBuffer源码深度剖析,布布扣,bubuko.com

时间: 2024-08-02 06:51:11

【源码】StringBuilder和StringBuffer源码深度剖析的相关文章

Java StringBuilder 和 StringBuffer 源码分析

简介 StringBuilder与StringBuffer是两个常用的操作字符串的类.大家都知道,StringBuilder是线程不安全的,而StringBuffer是线程安全的.前者是JDK1.5加入的,后者在JDK1.0就有了.下面分析一下它们的内部实现. 继承关系 public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence public

CharSequence,String ,Stringbuilder和StringBuffer源码分析

1.从类的定义看CharSequence.StringBuffer.StringBuilder.String的关系 下面先贴上这四者的定义(来自JDK1.6) CharSequence是一个定义字符串操作的接口,StringBuffer.StringBuilder.String中都实现了这个接口. //CharSequence定义 public interface CharSequence //StringBuffer定义 public final class StringBuffer exte

【nodejs原理&amp;源码赏析(4)】深度剖析cluster模块源码与node.js多进程(上)

目录 一. 概述 二. 线程与进程 三. cluster模块源码解析 3.1 起步 3.2 入口 3.3 主进程模块master.js 3.4 子进程模块child.js 四. 小结 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文目录 华为云社区地址:[你要的前端打怪升级指南] 一. 概述 cluster模块是node.js中用于实现和管理多进程的模块.常规的node.js应用程序是单线程单进程的,这也意味

String、StringBuilder、 StringBuffer 深入分析 源码解析

java学习有一段时间了,但学习的东西都是框架等东西,java基础知识有点遗忘,所以重温一下java基础知识,写写文章里面有错的希望大家指正共同进步~~ 一.String 大家经常会说使用"+"号连接String 字符串比StringBuffer慢,String类对象为不可变对象,一旦你修改了String对象的值,隐性重新创建了一个新的对象,那接下来我们详细分析一下为什么使用"+"号速度会慢,为什么String 对象是不可变对象: 1.final修饰类.引用变量.基

JDK 1.8 源码解析 String、StringBuilder和StringBuffer的异同

JDK提供了String.StringBuilder和StringBuffer这三个类来处理字符串,其中StringBuilder类是在JDK 1.5中新增的. 不同点如下: 1 是否有父类 String没有父类. // String类不能被继承 // 实现了Serializable.Comparable和CharSequence(字符序列)接口 public final class String implements java.io.Serializable, Comparable<Strin

libevent源码深度剖析

libevent 源码深度剖析,from: blog.csdn.net/sparkliang/article/category/660506 http://download.csdn.net/detail/sparkliang/2001038#comment http://libevent.org/

libevent 源码深度剖析十三

libevent 源码深度剖析十三 -- libevent 信号处理注意点 前面讲到了 libevent 实现多线程的方法,然而在多线程的环境中注册信号事件,还是有一些情况需要小心处理,那就是不能在多个 libevent 实例上注册信号事件.依然冠名追加到 libevent 系列. 以 2 个线程为例,做简单的场景分析. 1 首先是创建并初始化线程 1 的 libevent 实例 base1 ,线程 1 的 libevent 实例 base2 : 2 在 base1 上注册 SIGALRM 信号

libevent源码深度剖析二

libevent源码深度剖析二 --Reactor模式 张亮 前面讲到,整个libevent本身就是一个Reactor,因此本节将专门对Reactor模式进行必要的介绍,并列出libevnet中的几个重要组件和Reactor的对应关系,在后面的章节中可能还会提到本节介绍的基本概念. 1 Reactor的事件处理机制 首先来回想一下普通函数调用的机制:程序调用某函数?函数执行,程序等待?函数将结果和控制权返回给程序?程序继续处理. Reactor释义"反应堆",是一种事件驱动机制.和普通

libevent源码深度剖析四

libevent源码深度剖析四 --libevent源代码文件组织 1 前言 详细分析源代码之前,如果能对其代码文件的基本结构有个大概的认识和分类,对于代码的分析将是大有裨益的.本节内容不多,我想并不是说它不重要! 2 源代码组织结构 Libevent的源代码虽然都在一层文件夹下面,但是其代码分类还是相当清晰的,主要可分为头文件.内部使用的头文件.辅助功能函数.日志. libevent框架.对系统I/O多路复用机制的封装.信号管理.定时事件管理.缓冲区管理.基本数据结构和基于libevent的两