为什么字符串拼接StringBuilder要比string更好

抛出问题:如果需要循环拼接一个字符串几万次甚至几十万、上百万次。。。。等等,这种情况我们会发现string非常慢该如何解决?

看过另一篇误人子弟的文章,string面对大数量级的拼接会更快不过占用的内存比较大。自己实现发现string其实根本没法跟StringBuilder相比,而且占用空间。

使用StringBuilder(非线程安全)或者stringBuffer(线程安全)更快也更节省空间。

问题解决了,接下来我们了解一下为什么string和StringBuilder或stringBuffer之间的差距这么大?

我们可以通过查看string的源码看一下。

这里选择Java的源码进行查看,C#的代码大部分都对指针进行了操作,不利于理解(好吧我承认我看的一脸懵)。

首先string的源码:

public final class String
    implements java.io.Serializable, Comparable, CharSequence
  • java.io.Serializable

    这个序列化接口没有任何方法和域,仅用于标识序列化的语意。

  • Comparable

    这个接口只有一个compareTo(T 0)接口,用于对两个实例化对象比较大小。

  • CharSequence

              这个接口是一个只读的字符序列。包括length(), charAt(int index), subSequence(int start, int end)这几个API接口,值得一提的是,StringBuffer和StringBuild也是实现了该接口。

string的主要变量

    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

  public static final Comparator CASE_INSENSITIVE_ORDER
        = new CaseInsensitiveComparator();

 其中Value中存储的就是我们赋值给string的值,而Value使用final就标识就表示着无法随意修改,导致每一次拼接都是重新new 出来一个string,而原来的char又变成了碎片化的垃圾,导致不必要的内存损耗

而hash是String实例化的hashcode的一个缓存。因为String经常被用于比较,比如在HashMap中。如果每次进行比较都重新计算hashcode的值的话,那无疑是比较麻烦的,而保存一个hashcode的缓存无疑能优化这样的操作。

最后,这个CASE_INSENSITIVE_ORDER 其根本就是持有一个静态内部类,用于忽略大小写得比较两个字符串。

String支持多种初始化方法,包括接收String,char[],byte[],StringBuffer等多种参数类型的初始化方法。但本质上,其实就是将接收到的参数传递给全局变量value[]。

我们在来查看一下 length(),isEmpty(),charAt()这些方法的源码

    public int length() {
        return value.length;
    }

    public boolean isEmpty() {
        return value.length == 0;
    }
    
    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

知道了String其实内部是通过char[]实现的,那么就不难发现length(),isEmpty(),charAt()这些方法其实就是在内部调用数组的方法。

StringBuilder:

 在进行StringBuilder这个类的源码分析前,我们先来分析下一个抽象类AbstractStringBuilder,因为,StringBuffer和StringBuilder都继承自这个抽象类,即AbstractStringBuilder类是StringBuffer和StringBuilder的共同父类。AbstractStringBuilder类的关键代码片段如下:

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;//一个char类型的数组,非final类型,这一点与String类不同
 
    /**
     * 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];//构建了长度为capacity大小的数组
    }
 
}

这里就可以看到string和StringBuffer的区别,接下来我们在看一下StringBuffer

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
   /**
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuffer() {
        super(16);//创建一个默认大小为16的char型数组
    }
 
    /**
     * 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}
     *               argument is less than {@code 0}.
     */
    public StringBuffer(int capacity) {
        super(capacity);//自定义创建大小为capacity的char型数组
    }
//省略其他代码……
……

这里默认定义了一个数组长度为16的数组,在数组内数据达到一定长度后会进行扩充,扩充的规则为:“扩展”前数组长度的2倍再加上2 byte的规则来扩展。

我们直接看一下扩充部分的源码:

     /**
     * This implements the expansion semantics of ensureCapacity with no
     * size check or synchronization.
     */
    void expandCapacity(int minimumCapacity) {
       //“扩展”的数组长度是按“扩展”前数组长度的2倍再加上2 byte的规则来扩展
        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返回的新的char[]对象,从而达到“扩容”的特性
        value = Arrays.copyOf(value, newCapacity);
    }

总结:string为final修饰,而StringBuilder非final修饰.

看到这里我们就可以得知,为什么StringBuilder在面对字符串拼接的时候为什么要比string更适合了。

 

本文主要研究在面对大量字符串拼接的情况下选择哪种方式解决更合适。

记录一下工作遇到的小问题。如有问题,还请大牛不吝赐教。

版权声明:本文为博主原创文章,未经博主允许不得转载。

你可能感兴趣的:(Java,Java)