引言
在开发中,String字符串频繁的会被使用到,起着相当重要的作用。本篇通过对String基础知识的回顾,进而展开思考面试官常常会考字符串的那些知识。
字符串创建及存储机制
字符串的声明和初始化有以下两种情况:
(1)String s1 = new String(“abc”)
第一种可以拆分为两个过程:new String(“abc”)和赋值s1。new String(“abc”)在调用构造函数的时候会传入一个字符串常量,因此又可以拆分为"abc"和new String()两个操作。当字符串常量池中不存在"abc",则会先创建一个字符串常量"abc";若存在,则不在创建,在堆中创建一个String对象,该对象引用字符串常量池中的"abc"。
(2)String s1 = “abc”
第二种在创建的时候会先去查找字符串常量池是否存在相同的字符串,若已经存在,那么对象直接获取到字符串常量的引用;如果不存在,那么就需要先创建这个字符串常量对象,然后放入到字符串常量池中,再获得其引用。
下面我们通过画一下对象之间的引用关系,来进一步理解:
String、StringBuffer、StringBuilder三者之间的区别
可变性:
因为String类型是被final修饰的,所以String对象不可变。StringBuffer与StringBuilder都是集成AbstractStringBuilder类,它们都是使用char[]字符数组来保存字符串,因为没有被final修饰,所以它们是可变的。(附AbstractStringBuilder源码图)
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
线程安全性:
String类是final修饰的,为常量,所以线程安全。Stringbuffer的方法加了同步锁,所以是线程安全的,StringBuilder的方法没有加同步锁,所以非线程安全。
性能对比:
每次对String改变时,都会生成一个新的String对象,同时变量名指向新的对象。这样使得如果String频繁变化会导致内存中多了很多对象,会造成严重的资源浪费。
StringBuffer和StringBuilder都不会生成新的对象,而是对对象本身进行操作。相同情况下StringBuilder比StringBuffer性能上提升了10%-15%左右,但是要冒着线程不安全的危险。
使用场景对比:
字符串类型 | 使用场景 |
String | 操作数据量小,共享 |
StringBuffer | 单线程字符串缓冲区下操作大量数据 |
StringBuilder | 多线程字符串缓冲区下操作大量数据 |
解释一下为什么不能用String来保存经常修改的字符串:
如果我们对String字符串拼接形成新的字符串来使用,但是这个拼接后只是临时存放在字符串常量池中的一个字符串,如果我们每更改一次,就会生成一个,那么就会生成很多只使用一次然后无用的对象,这些无用的对象都会被垃圾回收器来回收,并且这些生成的对象会占据内存空间,这样就会影响到程序的性能。(看下面这个示例代码的例子)
public static void main(String[] args) {
String str = "hello";
System.out.println(str+" tom !");
System.out.println(str.toString());
}
输出:
hello tom !
hello