1、String分析
首先是创建一个String,可以看到一般有两种方法,就是Sting a = “abc”。另一种是String b = new String(“b”)
直接赋值
jvm为了提升性能和减少内存开销,避免字符的重复创建,其维护了一块特殊的内存空间,即字符串池,当需要使用字符串时,先去字符串池中查看该字符串是否已经存在,如果存在,则可以直接使用,如果不存在,初始化,并将该字符串放入字符创常量池中。使用String直接赋值,Sting a = "abc"可能创建一个或者不创建对象,如果”abc”在字符串池中不存在,会在java字符串池中创建一个String对象(”abc”),然后str指向这个内存地址,无论以后用这种方式创建多少个值为”abc”的字符串对象,始终只有一个内存地址被分配。==判断的是对象的内存地址,而equals判断的是对象内容。
使用new Sting创建
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/*其中value的属性是*/
private final char value[];
因为value是final类型,所以不可能再指向其他地方,所以每一次String指向一个新的目标,其都是新创建了一个value,也就是新创建了一个String类型的变量。
总结
直接赋值应该是比使用new Sting创建要好一点,因为如果内存空间中已经有了这个值,那么就不需要在重新申请空间来创建,而new Sting创建则是每一次都需要重新创建一个对象。但是如果每一次赋值的值在内存空间中都不会有,那么这两者就没有区别了。总的来说,String类型还是比较消耗性能的,因为他一般每次都需要重新创建,而不会复用之前的。
2、StringBuffer和Stringbuilder的区别
相信大家都知道他们两最大的区别就是Stringbuild是线程不安全的,Stringbuffer是线程安全的,但是效率StringBuild较StringBuffer高一些。所以在一些单线程或者保证不会有线程不安全的情况下,可以使用StringBuilder来最大化性能提升。
首先我们来看下StringBuilder,StringBuilder类继承了AbstractStringBuilder,并实现了Serializable,说明了这个类对象是可以进行网络间的数据传输的。
我们来看下StringBuilder类对象的初始化
public StringBuilder() {
super(16);
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
可以看出来创建一个StringBuilder对象就是创建了一个16byte大小的char数组,再来看下append操作是如何进行的
public StringBuilder append(String str) {
/*实际操作是在AbstractStringBuilder实现*/
super.append(str);
return this;
}```
```java
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
/*这个函数就是保证当前的char数组能够确保容纳下全部的字符
1、在第一次append时,count=0,len为新加入的字符串的长度,这时候因为一开始创建了16byte大小的数组,如果len<=16,则直接放入开始时创建的数组即可
如果len>16,这时候需要进行扩容,就是在申请一块更加大的空间,这时暂定的大小为34(原来的空间*2+2)注意后面会不断增大,然后比较len和34的大小,如果34较大,则先取34为申请的空间的大小,不然为len的长度为新申请的长度
注意这里还会判断是否越界这种情况,最大的空间可以为0x7fffffff-8
2、如果不是第一次append,则在原来的count的长度上继续申请,新增加的字符串的长度超过当前的char数组的容量时就进行扩容*/
ensureCapacityInternal(count + len);
/*将String的字符串放入value这个存放字符串的char数组中*/
str.getChars(0, len, value, count);
/*更新count的值*/
count += len;
return this;
}
然后再去看下StringBuffer的情况,发现和StingBuilder几乎一致,只有两处地方有些区别
/*1、第一个区别就是StringBuffer类的所有函数前都使用了synchronized这个重型锁来保证线程安全*/
public synchronized StringBuffer append(Object obj) {
/*2、多了一个toStringCache这个缓存,这个toStringCache是一个缓存,只要我们进行修改操作,这个缓存就会被刷新,其主要用在toString这个函数中*/
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
public synchronized String toString() {
/*第一次如果这个缓存为null,则复制一个最新的值到缓存中*/
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
/*之后如果没有进行更新操作,多次进行toString可以直接从缓存中取,而不需要每一次都进行一次拷贝
*/
return new String(toStringCache, true);
}
3、StringBuffer和StringBuilder比较String的优势
通过上面的分析可以看到,因为StringBuffer和StringBuilder都是维护了内存中的一个char数组,一开始会申请一段较大的空间,如果之后再不断append过程中发现char数组内存不够了,那么就会进行扩容,扩容的大小为(原来的大小*2+2),扩容的方式也一般是重新申请一段扩容后大小的空间,然后将原来的数据复制到新的内存空间中。
那么我们一般再往扩容后的数组中添加数据时,除非继续添加较多的数据,一般情况下,不会引发再次扩容,这时候就可以将数据直接放入到char数组中,但是如果是String类型,无论第二次添加多少数据,每一次都还是需要重新申请一段空间,然后将原来的数据和新添加的数据一起放入到新申请的空间中。这种情况下String的消耗就严重多了。
简单的说,StringBuffer和StringBuilder利用先申请一段较大的内存,如果新增加的数据在原来的内存空间还容纳的下,可以不用申请新的空间,自然也少了复制原来空间中数据的性能消耗。