美文网首页
关于StringBuilder内部实现详解

关于StringBuilder内部实现详解

作者: rainy618 | 来源:发表于2019-04-04 10:10 被阅读0次

在平常的开发都说对于字符串的拼凑时,要尽量使用StringBuilder来操作,特别是对于长字符串的拼凑。但是却很少知道为什么要这样做,在直接使用字符串进行拼凑时,编译器到底是怎么将"+"完成字符串拼凑的,它的弊端又是上面,而使用StringBuilder内部又是如何优化的呢?

现在我们来扒一扒其中的缘由,知其然而知其所以然。

首先我们来看下面这个例子:

public class StringDemo2{
        public static void main(String[] args) {
            String str = "";
            for (int i = 0; i < 10; i++) {
                str+=i;
            }
        System.out.println(str);
        }
  }

在拼凑str3字符串时,编译器在编译该.java文件时,帮我们添加了上面东西呢?我们通过javap指令来查看编译后.class指令文件

在编译期,编译器会帮我们生成一个StringBuilder对象,同时将使用+拼凑字符的过程,转换成调用StringBuilder的append( )方法进行拼凑。

这是有人会说内部的实现跟我们直接用StringBuilder对象进行拼凑是一样的啊,都是使用StringBuilder,都是使用append方法。其实细看还是有很大差别的,编译器并不会很智能,它只能将有+号操作的做出转换成StringBilder对象的append方法,作完这个+操作后,又调用了StringBuilder的toString()方法,该方法的内部是直接new String(.. )对象进行返回字符串的。接下来又继续重复该动作,进行拼凑字符串

这样不断的new StringBuilder() --> append()--->toString(), new StringBuilder() --> append()--->toString() ...的操作,将会创建大量的对象,而真正需要使用的字符串只是最后拼凑好的字符串,这样则造成的大量内存的浪费。

对于少量字符串的拼凑还算能接触,但对于大量且长文本的字符串则对于系统的开销就很大了。像这种不必要的内存浪费,我们当然要杜绝发生了。

而直接使用StringBuilder,拼凑字符串,其内部又是如何做的呢?

我们先将上面的例子改成StringBuilder来实现

public class StringDemo2{
    public static void main(String[] args) {
          String str = "";
          StringBuilder sb = new StringBuilder(str);
          for (int i = 0; i < 10; i++) {
                  sb.append(i);
          }
          System.out.println(sb.toString());
    }
}

同样适用javap查看文件指令:

从该字节码指令可以看出,在拼凑字符串时,只生成了一个StringBuilder对象用于拼凑,拼凑完后只生成了一个Sting对象,这样大大节省了内存开销,提高了效率。

由于String字符串是不可变的,每次的该包都会生成新的字符串,而StringBuilder内部是如何实现在内部append( )方法时提供复用性的呢?

我们来看到StringBuilder源码,首先StringBuilder继承至一个AbstractStringBuilder的抽象类。

在该AbstractStringBuilder抽象类中,在该类中有两个重要的成员变量:

其中char[ ] value就是用来实现字符串缓冲池的核心,count数是用来记录StringBuilder内部中真实的字符长度的。细心的小伙伴可能要问了,干嘛不直接使用char[] 数组的长度表示字符的长度呢?继续往下看就知道了。

在初始化一个StringBuilder对象是,如果使用个都是默认构造器进行初始化,在StringBuilder的默认构造器会调用父类抽象类的构造器,并传入默认16的数值,该数值用于初始化容缓冲池的大小,也就是初始化char[ ] 数组的长度,所有我们不能直接使用该长度来表示实际字符长度的。

调用append(String str)方法时,会调用其父类的append( )方法,所有要查看append( )方法的内部实现,还是得去AbstractStringBuilder类中

进入AbstractStringBuilder 类的append方法

我们发现,其内部的实现还是比较简单的。总的来说内部对append字符串的操作,在建立在char[ ]数组上的,当有新的字符串append进来时,会将该字符串转会成对应的char[ ]字符数组 ,然后调用System.copyArray( ... )方法将其拷贝至StringBuilder内部char[ ] value数组中。这样在拼凑字符串时,就不会像直接拼凑字符串那样不断创建新的String对象了。

在方法中还有一个内部字符容器扩容操作,ensureCapacityInternal(count + len);顺便我们来看出下该方法的操作,

minimumCapacity参数就是那个 count(原有的字符数) + len(新加入的字符数)得到的值,如果该值大于了char[] valueStringBuilder内部的缓冲数组的长度,则说明原有的容器已经不够装了,需要对其进行扩容操作。

将value原有的值都拷贝至一个新的char[ ] 数组中,该数组的长度是原来的2倍+2。从这也可以看出,在使用StringBuilder拼凑厂字符串时,应该预估一个StringBuilder内部char[ ]容器的初始化长短值,这样就可以尽量的避免频繁扩容带来的性能损耗。

相关文章

网友评论

      本文标题:关于StringBuilder内部实现详解

      本文链接:https://www.haomeiwen.com/subject/aitfiqtx.html