String、StringBuffer、StrignBuilder这三者的区别在面试中也是经常被问到的问题,笔者在之前的面试中就被问到了一次,在这里记录下来。这三者的主要区别体现在内存占用以及执行效率这两个方面上。
String
Sting 是大家最熟悉不过的了,被定义成一个不可变对象,在底层源码中String类是用了final修饰的,其属性中除了hash属性外其他属性都被定义成了final,这也就确定了String的不可变性,因此我们对String字符串进行一系列操作时,都会生成一些无用的中间对象,当我们操作的字符串多了就会严重的占用我们的内存空间影响性能,虽然适用String来声明字符串常量简单明了,但是String适用于操作的字符串较少的情况。
下面通过一小段代码来说明String字符串的不可变性
@Test
public void test1(){
//验证String字符串的不可变性
String s1="abc";
//将s1的引用地址赋给s2,此时s1,s2指向同一块内存区域
String s2=s1;
//改变s1的值
s1="def";
//输出s1 s2
System.out.println("s1="+s1);//输出def
System.out.println("s2="+s2);//仍输出abc
}
在上述代码中当执行到String s2=s1;时s1和s2都指向的是"abc"这块内存地址,理论上改变s1的值,s2的值也要改变才对,但是从输出结果中却不是如此,在我们对s1重新赋值为“def”的时候,会首先从字符串常量池中找是否有“def”的字符串,如果有则将s1直接指向它,如果没有则在字符串常量池中新建一个"def"的字符串,并且将s1指向它,此时字符串常量池中的"abc"仍然是存在的,只是此时的s1指向了"def"这一块,而"abc"这块是由s2指向,所以s1的改变并不会影响旧的字符串,而是新建一个新的字符串,并且由旧的指向新的,旧的字符串仍存在内存中。所以当我们对大量字符串进行操作时,就会产生很多旧的无用字符串在内存中。
StringBuffer
StringBuffer产生的是一个可变对象,由StringBuffe声明的字符串,每次对象进行操作时都是对原字符串进行操作,并不会生成无效的中间字符串,这样就解决了String字符串带来的过多的中间变量而导致内存占用问题。事实上StringBuffer也是为了解决这一问题才出来的。
StringBuffer 中定义的方法都是synchronized修饰的同步方法保证了线程的安全,适合用在多线程环境中,但是线程安全必然要以性能为代价。StringBuffer 提供了append(),insert(),replace()等方法方法,用来对字符串进项操作,操作的字符串对象是原对象。
StringBuilder
StringBuilder与StringBuffer的最大区别就在于StringBuilder是线程不安全的,适用于单线程环境中,因此他的执行效率要比StringBuffer更快。在单线程环境下建议使用执行效率比较快StringBuilder.
虽然String、StringBuilder和StringBuffer这三个类都是用final修饰的,并且底层都是使用char数组实现的,但不同的是StringBuilder和StringBuffer都继承了AbstractStringBuilder这个类,在这个类中的数组只是一个普通的字符数组,而在String类中的字符数组是被final修饰的。
总结
1、String在进行字符串操作时,会产生过多的无用中间对象,适用于少量的字符串操作
2、在多线程环境中应该使用StringBuffer来声明字符串,而在单线程环境中建议使用效率更快的StringBuilder。
3、StringBuffer是线程安全的,效率低,而StringBuilder是线程不安全的,效率高