1.String
String一旦创建后就不会更改。
string s = "abc"; s = "123";
当s被赋值时,它并不会直接赋值,
- 首先会去查找字符串池,如果字符串池有这个字符串,那么直接将当前变量指向字符串池内的字符串。
- 如果字符串池内没有这个字符串,那么在堆上创建一块内存用于放置这个字符串,并将当前变量指向这个新建的字符串。
而是在堆上开辟空间,存放"123"这个数据,之后改变引用地址,指向"123",而原来的数据则是放在堆上等待GC。由此可见,当它进行拼接的时候,也只是创建一个空间存放新的数据,一旦数量庞大就会造成大量的GC。
为什么字符串一定要是不可变的?
- 线程安全。在多线程环境下,只有对资源的修改是有风险的,而不可变对象只能对其进行读取而非修改,所以是线程安全。如果字符串是可修改的,那么在多线程环境下,需要对字符串进行频繁加锁,这是比较影响性能的。
- 为了安全。
2.StringBuilder
这要从StringBuilder的底层开始说起,StringBuilder的底层与string一样都是字符数组(即char[]),与string被设计为不可变不同的是,StringBuilder是可变的。
当StringBuilder进行连接操作时,它会经历以下步骤:
- 检查当前字符数量是否大于长度,如果大于,那么对StringBuilder进行扩容。
- 向char[]数组后面添加字符
很显然,只有在StringBuilder长度小于添加的字符时,才会额外申请内存对char[]数组进行扩容,其他情况下,就是对数组内的元素进行变换而已,与string类型每次连接都会废弃掉一个对象相比,StringBuilder就显得更快一些了。
当然,除了连接操作,StringBuilder还支持删除、修改字符串,这当然也是根据其中的char []数组进行操作的(而字符串因为其不可变性,是不支持这些操作的)。
考虑到StringBuilder扩容也是会产生GC的,所以一般比较好的做法是,在StringBuilder创建时就根据之后的使用情况为其指定一个容量。