String,StringBuffer,StringBuilder深入分析

String,StringBuffer,StringBuilder深入分析

1、String

Java设计的一个不可变类,实现接口有Serializable,Comparable, CharSequence

1.1String对象创建

String对象创建过程有别于其他对象,String是我们在程序中使用最多的一个类型,所以java在设计之初增加了一个常量池

当使用字面量定义对象String a = “abc”; 的时候,JVM不会在堆中分配空间,而是在常量池中判断”abc”是否存在,如果存在则直接引用,如果存在则在常量池创建”abc”.

当使用关键字new创建对象的时候,String b = new String(“abc”);

JVM会先判断常量池中是否存在改字符串,如存在则在堆中复制该对象的副本,将堆中对象的地址赋值给b;所以当new String()的时候如果字符串不存在常量池中,则会产生两个对象,一个常量池对象,一个堆对象

1.2String类属性

查看String源代码(JDK1.8)可以得知,其主要有两个属性

private final char[] value;

private int hash;

String底层维护的是char[]

查询资料可得字符串常量池底层为key-value结构,key为String的hash 值,所以在查找字符串的时候能迅速定位

1.3String常用拼接方法

concat方法主要用于拼接字符串,观察源代码可以得知,其拼接过程为新建一个char[]并将原字符串和拼接字符串copy到新的数组中,并生成新的字符串对象,由此可以得知大量字符串拼接会一直重复创建char[]且拼接后原字符串被丢弃,大量拼接操作并不适合使用该方法

当使用+拼接字符串的时候会有以下几种情况:

第一种

String a = "ab" + "cd";  //1个对象

String b = "abcd";

System.out.println("a = b : "+ (a == b)); // true

反汇编之后分析得出:由于常量的值在编译的时候就被确定(优化)了,所以a的值在编译期间就已经确定为abcd,此时为字面量赋值,abcd先在常量池中查找,不存在所以存放在常量池中,并引用给a,  给b赋值时,发现abcd已经存在常量池中,所以不在创建,直接引用。

第二种

String a = "abcde";

String b = "abcd";

String c = a+b;

String d = c + "";

反汇编之后分析得知,在+连接过程中,编译器优化代码,产生了一个StringBuilder对象,并对拼接的字符串做append操作,最后toString得到最终的字符串。且每次使用新对象接收拼接结果的时候都会产生一个新的StringBuilder对象

1.4效率分析

数据量10000

=============plus===============

长度:38890

耗时:549

=============concat===============

长度:38890

耗时:129

=============StringBuffer===============

长度:38890

耗时:1

=============StringBuilder===============

长度:38890

耗时:1

数据量100000

=============plus===============

长度:488890

耗时:34172

=============concat===============

长度:488890

耗时:9412

=============StringBuffer===============

长度:488890

耗时:7

=============StringBuilder===============

长度:488890

耗时:4

可以看出在达到十万数据量时plus和concat方式已经不适合参与分析

数据量1000000

=============StringBuffer===============

长度:5888890

耗时:114

=============StringBuilder===============

长度:5888890

耗时:64

数据量10000000

=============StringBuffer===============

长度:68888890

耗时:899

=============StringBuilder===============

长度:68888890

耗时:668

2、StringBuffer,StringBuilder源码分析

两者都extends AbstractStringBuilder implements Serializable, CharSequence

从源码看底层结构基本一致,通过string拼接效率分析来看,StringBuffer比起StringBuilder还是要慢上一些,查看源码得知StringBuffer所有方法都加上了synchronized,保证了其在多线程环境下没有线程同步问题,所以在多线程操作环境下应该选用StringBuffer,在单线程下应该选用StringBuilder保证更高的效率。

构造方法分析:

public StringBuilder() {

    super(16);

}

AbstractStringBuilder(int capacity) {

    value = new char[capacity];

}

其底层也和String对象一样维护一个char[],不同之处在于String维护的数组是

private final char[] value;不可变的

且StringBuilder在初始化的时候如果没有指定初始容量,默认赋值16个字符长度

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方法可以得知

1.在append(null)的时候,会在字符串后面拼接’null’字符串

2.在每次追加字符串的时候都会ensureCapacityInternal判断空间是否足够

查看ensureCapacityInternal方法

当空间不足够时会先将其底层数组扩容一倍,并将原先数据复制到新的数据,然后再追加 字符串,由此可以得知,当我们在进行大量字符串拼接的时候,应该先预估其长度,给出 一个足够的容量值,减少底层数组因扩容浪费的内存和时间

优化StringBuffer和StringBuilder拼接字符代码

=============StringBuffer===============

长度:68888890

耗时:723

=============StringBuilder===============

长度:68888890

耗时:565

较前面未初始容量效率有百分二十的提升。


你可能感兴趣的:(String,StringBuffer,StringBuilder深入分析)