String,StringBuffer,StringBuilder深入分析
1、String
Java设计的一个不可变类,实现接口有Serializable,Comparable
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
较前面未初始容量效率有百分二十的提升。