25java的StringBuilder类和StringBuffer类

  • Post author:
  • Post category:java



StringBuilder线程不安全,效率高;StringBuffer线程安全,效率低

new StringBuilder创建长度为16的字符串数组

下面看程序实例:

package testString;

public class stringBuilder {
    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder();//字符数组初始长度为16
        StringBuilder stringBuilder1 = new StringBuilder(32);//指定字符数组初始长度是32
        StringBuilder stringBuilder2 = new StringBuilder("tyuiop");//字符数组初始长度是"tyuiop"+16=6+16=22
        stringBuilder2.append("#fghjkl");
        System.out.println(stringBuilder2);
        stringBuilder2.append("#1").append("#fada").append("#发达");//通过return this实现方法链
        System.out.println(stringBuilder2);
        for (int i = 0; i < 10; i++) {
            stringBuilder.append(i);
        }
        System.out.println(stringBuilder);
    }
}

运行结果:
在这里插入图片描述

程序分析:StringBuilder是位于package java.lang;包下的类,是可变长数组的类。



1. StringBuilder stringBuilder = new StringBuilder(); 创建一个可变长字符数组,但是这个字符数组对象是由默认长度的,源码如下:

public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/**
     * Constructs a string builder with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuilder() {
        super(16);
    }

其中super(16);调用父类的构造方法,并传参16,我们看父类的有参构造器源码:

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * 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(int capacity) {
        value = new char[capacity];
    }
}

由此可见,这个可变长字符数组的初始长度是16。



2. StringBuilder stringBuilder1 = new StringBuilder(32); 直接值得顶字符数组的初始长度为32。源码如下:

    /**
     * Constructs a string builder with no characters in it and an
     * initial capacity specified by the {@code capacity} argument.
     *
     * @param      capacity  the initial capacity.
     * @throws     NegativeArraySizeException  if the {@code capacity}
     *               argument is less than {@code 0}.
     */
    public StringBuilder(int capacity) {
        super(capacity);
    }

调用父类的有参构造方法,并传递参数用户指定长度capacity。



3.

StringBuffer stringBuffer2 = new StringBuffer(“tyuiop”);

字符数组初始长度是”tyuiop”+16=6+16=22。这个从初始化的字符数组的内容是:{‘t’,‘y’,‘u’,‘i’,‘o’,‘p’,\u0000,\u0000,…}。源码如下:

    /**
     * Constructs a string builder initialized to the contents of the
     * specified string. The initial capacity of the string builder is
     * {@code 16} plus the length of the string argument.
     *
     * @param   str   the initial contents of the buffer.
     */
    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }

其中super();方法

    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

其中append()方法:

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

其中super.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方法的功能就是拼接字符串:把给定字符串拼接到引用对象的后面。

    观察发现每个append方法里面最后面都有

    return this

    语句,

    this代表当前调用这个方法的对象

    ,也就是谁调用这个方法就返回谁,使用this关键字的好处是可以支持方法链。



下面的代码是如何append(添加,追加)字符数组的?

StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10; i++) {
    stringBuilder.append("#");//在此处设置断点进行debug
}

当程序运行到断点处时,进入位于StringBuilder类中的append方法,传递的参数是字符”#“,下面看这个append方法的源码:

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

可以看到在此处有调用StringBuilder类的父类的append方法,此时传递的参数是字符”#“那么我们进入StringBuilder类的父类的append方法中:

    /**
     * Appends the specified string to this character sequence.
     * <p>
     * The characters of the {@code String} argument are appended, in
     * order, increasing the length of this sequence by the length of the
     * argument. If {@code str} is {@code null}, then the four
     * characters {@code "null"} are appended.
     * <p>
     * Let <i>n</i> be the length of this character sequence just prior to
     * execution of the {@code append} method. Then the character at
     * index <i>k</i> in the new character sequence is equal to the character
     * at index <i>k</i> in the old character sequence, if <i>k</i> is less
     * than <i>n</i>; otherwise, it is equal to the character at index
     * <i>k-n</i> in the argument {@code str}.
     *
     * @param   str   a string.
     * @return  a reference to this object.
     */
    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;
    }

从上述的源码中发现,这个方法的功能就是先进行原来的字符数组的空间判断,如果空间足够就直接追加,如果不够就按照

一定的规则

进行扩展,然后再添加。获取完新增字符串的长度之后,进入校验方法ensureCapacityInternal()进行校验原字符数组的空间够不够,源码如下:

    /**
     * For positive values of {@code minimumCapacity}, this method
     * behaves like {@code ensureCapacity}, however it is never
     * synchronized.
     * If {@code minimumCapacity} is non positive due to numeric
     * overflow, this method throws {@code OutOfMemoryError}.
     */
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

当for循环执行第一次的时候,len = str.length()=1,count=0,所以传递给ensureCapacityInternal() 方法的参数minimumCapacity=1,显然minimumCapacity不可能大于value.length(=16,初始大小), 那么就不创建新的字符数组。下一步执行str.getChars(0, len, value, count)方法,并传递复制字符串的相关参数,下面是getChars()源码:

/**
     * Copies characters from this string into the destination character
     * array.
     * <p>
     * The first character to be copied is at index {@code srcBegin};
     * the last character to be copied is at index {@code srcEnd-1}
     * (thus the total number of characters to be copied is
     * {@code srcEnd-srcBegin}). The characters are copied into the
     * subarray of {@code dst} starting at index {@code dstBegin}
     * and ending at index:
     * <blockquote><pre>
     *     dstBegin + (srcEnd-srcBegin) - 1
     * </pre></blockquote>
     *
     * @param      srcBegin   index of the first character in the string
     *                        to copy.
     * @param      srcEnd     index after the last character in the string
     *                        to copy.
     * @param      dst        the destination array.
     * @param      dstBegin   the start offset in the destination array.
     * @exception IndexOutOfBoundsException If any of the following
     *            is true:
     *            <ul><li>{@code srcBegin} is negative.
     *            <li>{@code srcBegin} is greater than {@code srcEnd}
     *            <li>{@code srcEnd} is greater than the length of this
     *                string
     *            <li>{@code dstBegin} is negative
     *            <li>{@code dstBegin+(srcEnd-srcBegin)} is larger than
     *                {@code dst.length}</ul>
     */  
	public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

前面三个if语句是进行安全性校验,防止传入不合法的索引数据,如果所有的数据都合法,那么就执行System.arraycopy()方法,这个方法是把新增加的那个字符串追加到原来的字符数组中。具体分析一下System.arraycopy()方法:value是原来创建的字符数组(还未扩容),srcBegin=0,dst是getChar方法传递的参数value[],srcEnd是getsChar方法传递的参数len,每次追加的都是一个#,所以len=1=srcEnd,dstBegin是getChar方法传递的参数count,随着循环次数增加而增加,srcEnd – srcBegin表示的是len-0,也即是追加的字符串的长度,那么这个方法就是把value[](是String类里面的value[])的从srcBegin索引开始的元素复制到dst[]的从dstBegin索引开始共srcEnd – srcBegin个字符的储存空间中。注意:dst[]是形参,它的实参是value[](是StringBuilder里面的value[] ;)。下面我理解的是,每次循环(StringBuilder类的对象存储空间足够)都会调用一次System.arraycopy()方法进行复制,就是新创建一个String类的字符数组对象存储传递过来的字符串常量(待添加的字符串),然后从头直接复制到StringBuilder类的对象value[]中。

当循环到第17次时(i=16),此时的可变长字符数组的存储空间已经不够用了,需要拓展空间,进入ensureCapacityInternal()方法中

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

newCapacity()方法的源码是:

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

(value.length << 1)中的“<<”是把这个数的二进制左移一位,左移一位就是数值乘以2,右移一位就是数字除以2,也就是说这里把原来的字符数组的长度扩大为原来的2倍加2,最后返回值处使用的是一个三元运算符:简单分析,返回值是newCapacity,就是新的字符数组长度。

PS:移位运算

二进制 十进制
0000 0010 2
左移一位:0000 0100 4
0000 1100 12
右移一位:0000 0110 6

然后调用Arrays.copyOf()方法,源码如下:

    public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

可以看到直接创建一个大小为newLength(newCapacity)的新的字符数组copy[],然后还是调用System.arraycopy()方法,把原来的value[]数组里面的所有元素复制到copy[]数组中,最终赋值给value[]数组。




StringBuffer类


  • StringBuilder线程不安全,效率高;StringBuffer线程安全,效率低

  • StringBuffer类的方法种类与StringBuilder类的方法完全相同,用法也相同,只是StringBuffer类的方法都被synchronized关键词修饰,表示线程安全,调用这里的方法必须要有对象锁,涉及到多线程问题,后续再学习。


您可能会好奇为什么博客标题前面要加上数字?是这样的:本人之前在博客园发布自己的学习java的过程,标号以前的博客都那里了,为了不做无用功就不一一搬运,所以就在这边沿用那里的标号,欢迎您去博客园阅读并指出我的博客内容的不足之处。

  • 我的博客园地址:https://www.cnblogs.com/fanfada/
  • 记录了一个小白自学java的旅程



版权声明:本文为fada_is_in_the_way原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。