(5)java 字符串 从源代码的角度聊聊java中StringBuffer、StringBuilder、String中的字符串拼接

来源:互联网 发布:荣耀手环zero每天数据 编辑:程序博客网 时间:2024/04/29 13:23

从源代码的角度聊聊java中StringBuffer、StringBuilder、String中的字符串拼接

长久以来,我们被教导字符串的连接最好用StringBuffer、StringBuilder,但是我们却不知道这两者之间的区别.跟字符串相关的一些方法中总是有CharSequence、StringBuffer、StringBuilder、String,他们之间到底有什么联系呢?

1、从类的定义看CharSequence、StringBuffer、StringBuilder、String的关系

下面先贴上这四者的定义(来自JDK1.6) 

注意 String类  StringBuffer类  StringBuilder类  都是 public final修饰的。  

CharSequence是一个定义字符串操作的接口,StringBuffer、StringBuilder、String中都实现了这个接口.

//CharSequence定义public interface CharSequence//StringBuffer定义 public final class StringBuffer    extends AbstractStringBuilder    implements java.io.Serializable, CharSequence//StringBuilder定义public final class StringBuilder    extends AbstractStringBuilder    implements java.io.Serializable, CharSequence//String定义public final class String    implements
java.io.Serializable, Comparable<String>, CharSequence

String 是java中的字符串,它继承于CharSequence。 

String 和 CharSequence 关系 
String 继承于CharSequence,也就是说String也是CharSequence类型。 
CharSequence是一个接口,它只包括length(), charAt(int index), subSequence(int start, int end)这几个API接口除了String实现了CharSequence之外,StringBuffer和StringBuilder也实现了CharSequence接口。 

       也就是说,CharSequence其实也就是定义了字符串操作的接口,其他具体的实现是由String、StringBuilder、StringBuffer完成的,String、StringBuilder、StringBuffer都可以转化为CharSequence类型。

StringBuilder 和 StringBuffer 的区别

StringBuilder 和 StringBuffer都是可变的字符序列。它们都继承于AbstractStringBuilder,实现了CharSequence接口。 
但是,StringBuilder是非线程安全的,而StringBuffer是线程安全的。

它们之间的关系图如下: 

 public interface CharSequence {
    int length();
    char charAt(int index);
    CharSequence subSequence(int start, int end);
    public String toString();
}

String类

  private final  char value[];  //其内部char[]   char数组用了final修饰符修饰

  private final int offset;

  private final int count;

StringBuilder类 StringBuffer类 属性 继承 自AbstractStringBuilder 类

 char value[];   //这个char数组并没有使用final修饰  这是与string中字符数组的区别

 int count;

AbstractStringBuilder类的构造函数

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

StringBuilder类构造函数

public StringBuilder() {
       super(16);                //调用父类的构造函数 默认capacity传值16   即初始化大小为16的char[]
    }

public StringBuilder(int capacity) {
        super(capacity);     //调用父类的构造函数   由自己指定char数组的大小即capacity
    }

public StringBuilder(String str) {
          super(str.length() + 16); //构造了char数组 大小为  str.length() + 16   
           append(str);                  //同时将str 装到刚刚 新建立的char数组中去 使用append方法
    }

----------------StringBuilder 相比String 多了一个capacity() 方法  这个方法返回的是当前字符数组的大小 --------------

public int capacity() {   
       return value.length; //Returns the current capacity
    }

----------------StringBuilder 相比String length方法都一样 返回的是count属性 即字符串的长度 ------------- 

 public int length() {
         return count;
    }

2、从构造函数到具体的字符串拼接操作看看String、StringBuffer、StringBuilder的区别

下面我们来分析一下String、StringBuffer、StringBuilder具体的构造函数,了解他们是怎么构造出来的,再看看具体的字符串连接操作。

(1)String

String的构造函数(几个常见的构造函数)

    public String() {        this.offset = 0;        this.count = 0;        this.value = new char[0];      }    /**     * Initializes a newly created {@code String} object so that it represents     * the same sequence of characters as the argument; in other words, the     * newly created string is a copy of the argument string. Unless an     * explicit copy of {@code original} is needed, use of this constructor is     * unnecessary since Strings are immutable.     *     * @param  original     *         A {@code String}     */    public String(String original) {        int size = original.count;        char[] originalValue = original.value;        char[] v;        if (originalValue.length > size) {              // The array representing the String is bigger than the new            // String itself.  Perhaps this constructor is being called            // in order to trim the baggage, so make a copy of the array.            int off = original.offset;            v = Arrays.copyOfRange(originalValue, off, off + size);        } else {            // The array representing the String is the same            // size as the String, so no point in making a copy.            v = originalValue;        }        this.offset = 0;        this.count = size;        this.value = v;    }    /**     * Allocates a new {@code String} so that it represents the sequence of     * characters currently contained in the character array argument. The     * contents of the character array are copied; subsequent modification of     * the character array does not affect the newly created string.     *     * @param  value     *         The initial value of the string     */    public String(char[] value) {        this.offset = 0;        this.count = value.length;        this.value = StringValue.from(value);    }

再看看String中具体的Concat函数

public String concat(String str) {        int otherLen = str.length();        if (otherLen == 0) {            return this;        }        char[] buf = new char[count + otherLen]; //新new了一个 字符数组,其长度是两字符串长度的和        getChars(0, count, buf, 0);              //将自身内容存储进 buf字符数组中去        str.getChars(0, otherLen, buf, count);   //将参数str的内容存储进 buf字符数组中去 
        return new String(0, count + otherLen, buf); //将 完成内容拼装的 char数组 封装成String 返回    }

getChars

public void getChars(int srcBegin,int srcEnd, char[] dst,int dstBegin)
将字符从此字符串复制到目标字符数组

要复制的第一个字符位于索引 srcBegin 处;要复制的最后一个字符位于索引 srcEnd-1 处(因此要复制的字符总数是srcEnd-srcBegin)。要复制到dst 子数组的字符从索引dstBegin 处开始,并结束于索引:     dstbegin + (srcEnd-srcBegin) - 1

参数:
srcBegin -  字符串中要复制的第一个字符的索引。
srcEnd -     字符串中要复制的最后一个字符之后的索引。
dst -          目标数组。
dstBegin - 目标数组中的起始偏移量。 

getChars的代码   

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > count) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }

       //最终调用System.arraycopy方法
        System.arraycopy(value, offset + srcBegin, dst, dstBegin,srcEnd - srcBegin);
    }

System类中的arraycopy方法(native 涉及到JNI)

      public staticnativevoid arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length);

----------------------------------------------------------------------------------

从Concat函数中,我们可以知道在对字符串使用concat操作后

具体的操作是new出一个等同于两个字符串连接总长度的新的char数组

然后将两个字符串复制到新的char数组中,然后返回一个新的String对象。

(2)StringBuilder

StringBuilder常见构造函数

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

从StringBuilder的构造函数中,我们可以看见StringBuilder直接调用父类(AbstractStringBuilder)的构造函数

我们再看看AbstractStringBuilder的构造函数

abstract class AbstractStringBuilder implements Appendable, CharSequence {    final static int[] sizeTable = {            9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999,            Integer.MAX_VALUE        };    /**     * The value is used for character storage.     */    char[] value;    /**     * The count is the number of characters used.     */    int count;    /**     * This no-arg constructor is necessary for serialization of subclasses.     */    AbstractStringBuilder() {    }    /**     * Creates an AbstractStringBuilder of the specified capacity.     */    AbstractStringBuilder(int capacity) {        value = new char[capacity];      }
//其他的一些逻辑}

从AbstractStringBuilder的构造函数中,我们可以看出

StringBuilder中存储字符串其实用的是一个char数组,capacity其实就是指定这个char数组的大小。

下面我们再从StringBuilder中的append函数看看他具体是怎么做的(以 append(String str) 为例看看)。

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

又是直接调用父类(AbstractStringBuilder)的append方法,再跟到父类中去看看。

    /**     * value 用来存储字符串.     */    char value[];    /**      * 有效字符串的数目.     */    int count;    public AbstractStringBuilder append(String str) {        if (str == null) {               str = "null";        }        int len = str.length();        if (len == 0) {            return this;        }        int newCount = count + len;             if (newCount > value.length) {  //查验当前char数组的长度是否足够放下str 放不下扩容expandCapacity,放得下不扩容。            expandCapacity(newCount);        }       //getChars将字符串复制到指定的位置        str.getChars(0, len, value, count); //从count位置开始 将str的内容放进value字符数组中         count = newCount;                   //重新标记char[]的  长度        return this;    }

上面的逻辑还是比较简单的,在append(str)函数调用的时候首先会判断原来用于存储字符串的values的字符串数组有没有足够的大小来存储将要新添加入StringBuilder的字符串。如果不够用,那么就调用expandCapacity(int minimumCapacity)让容量翻两倍(一般是扩大两倍,特殊情况见代码),如果够用,那么就直接添加进去。

/**     * This implements the expansion semantics of ensureCapacity with no     * size check or synchronization.     */    void expandCapacity(int minimumCapacity) {        int newCapacity = (value.length + 1) * 2;   //
 (value.length + 1) * 2; 当前容量+1 再两倍

if (newCapacity < 0) {  
             newCapacity = Integer.MAX_VALUE;       
        } else if (minimumCapacity > newCapacity) {         
             newCapacity = minimumCapacity;        
        }        
        value = Arrays.copyOf(value, newCapacity);   //将原有的字符数组 复制值到新的数组中去新的数组大小为 newCapacity
}

(3)StringBuffer

StringBuffer的构造函数

/**     * Constructs a string buffer with no characters in it and an      * initial capacity of 16 characters.      */    public StringBuffer() {       super(16);    }    /**     * Constructs a string buffer with no characters in it and      * the specified initial capacity.      *     * @param      capacity  the initial capacity.     * @exception  NegativeArraySizeException  if the <code>capacity</code>     *               argument is less than <code>0</code>.     */    public StringBuffer(int capacity) {       super(capacity);    }

StringBuffer也是直接调用父类(AbstractStringBuilder)的构造函数,那么我们从上面的分析中,就可以知道StringBuffer其实也是利用char[]类型的数组来保存字符串数组的。

再看看StringBuffer的append函数    注意synchronized 修饰append方法

public synchronized StringBuffer append(String str) {        super.append(str);        return this;    }

还是调用父类的append函数,但是在这里有值得注意的地方,StringBuffer的append函数有一个synchronized标识符,也就是说StringBuffer中的append函数是线程安全的,通过继续查阅其他StringBuffer中的函数,我们也可以发现他们有synchronized标识符,这就不难理解为什么StringBuffer是线程安全的,但是很明显加上线程控制会拖慢程序运行的速度,所以如果不需要线程控制,那么最好就用StringBuilder。

//下面只是节选一些StringBuffer中的函数synchronized StringBuffer     append(char ch)synchronized StringBuffer     append(char[] chars)synchronized StringBuffer     append(char[] chars, int start, int length)synchronized StringBuffer     append(Object obj)synchronized StringBuffer     append(String string)synchronized StringBuffer     append(StringBuffer sb)synchronized StringBuffer     append(CharSequence s)synchronized StringBuffer     append(CharSequence s, int start, int end)synchronized StringBuffer     insert(int index, char ch)synchronized StringBuffer     insert(int index, char[] chars)synchronized StringBuffer     insert(int index, char[] chars, int start, int length)synchronized StringBuffer     insert(int index, String string)StringBuffer     insert(int index, Object obj)

后记:

可能很多会想了解String中的+和StringBuilder.append的效率,以及纠结要用哪个。我在网上发现有人已经写了一篇文章,分享给大家在Java中连接字符串时是使用+号还是使用StringBuilder。

 ------------------------------------------------------------------------------------------

作者:kissazi2 
出处:http://www.cnblogs.com/kissazi2/ 
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

0 0
原创粉丝点击