String、StringBuffer 和StringBuilder
String、StringBuffer 和StringBuilder的区别,这个问题在Java初级面试中经常被问到,下面我们来了解一下它。
区别:
1.String 长度不可变,而 StringBuilder 和 StringBuffer 长度可变。
2.它们的执行速度不同:StringBuilder > StringBuffer > String
3.StringBuilder 线程不安全 和 StringBuffer 线程安全
原因1:
String 长度不可变,而 StringBuilder 和 StringBuffer 长度可变
源码:
String
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. 该值用于字符存储。*/
private final char value[];
// ......
}
StringBuilder
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{....}
StringBuffer
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{....}
AbstractStringBuilder
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/** 该值用于字符存储。
* The value is used for character storage.
*/
char[] value;
// .....
}
分析:
从以上源码中我们可以看到,String 这个类底层使用了 final 修饰的长度不可变的字符数组,所以它长度不可变,
private final char value[];
而 StringBuilder 和 StringBuffer 都继承自 AbstractStringBuilder ,且AbstractStringBuilder 底层使用的是可变字符数组,所以二者长度不变。
原因2:
它们的执行速度不同:StringBuilder > StringBuffer > String
我们先看一下下面这段代码:
代码:
package com.itheibai.test;
public class StringTest {
public static void main(String[] args) {
String str = "abc";
System.out.println("str 连接之前:" + str);
System.out.println("str 哈希值:" + str.hashCode());
str = str + "de";
System.out.println("str 连接之后:" + str);
System.out.println("str 哈希值:" + str.hashCode());
}
}
执行结果:
分析:
整个程序执行完,从表面上看,似乎我们改变了 str 对象的字符串长度,与源码不符。但从 str 的哈希值来看,两次输出的哈希值并不相同,也就是说拼接后的字符串对象,和原来的字符串对象,并不是同一个对象。什么原因呢?
其实 JVM 是先创建了一个 str 对象,将 “abc”赋值给 str ,然后在内存中有创建了第二个 str 对象,将拼接后的字符串 “abcde”赋值给第二个 str 对象,此时 Java 虚拟机的垃圾回收机制开始工作,将第一个 str 对象回收。
所以,String类型的字符串要完成“改变长度”这样的操作,就需要不断地创建再回收,创建再回收,无形中经过了很多步骤,而 StringBuilder 和 StringBuffer 数组可变,直接可进行更改,所以要更快。
而 StringBuilder 为什么比 StringBuffer 要快呢?
先看一下源码:
源码:
StringBuilder
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
StringBuffer
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
分析:
从源码中我们可以看出,StringBuffer 的 append 方法都被 synchronized 关键字修饰了(不止上面这些方法,源码中的所有方法都被 synchronized 修饰了),
synchronized 关键字是给线程加锁,加锁是会损耗性能的,所以 StringBuilder 要比 StringBuffer 快。
原因3:
StringBuilder 线程不安全 和 StringBuffer 线程安全
分析:
结合原因2的源码进行分析,StringBuffer 对象的所有方法都被 synchronized 关键字修饰,给线程加了锁,所以线程安全。继续往下看源码:
StringBuffer
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
StringBuilder
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
可以看出,StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串,而 StringBuilder 是每次都需要复制一次字符数组,在构造一个字符串。
所以,缓冲区这也是对 StringBuffer 的一个优化吧,不过 StringBuffer 的这个 toString 方法仍然是同步的。
可以这样理解,如果一个 StringBuffer 对象的字符串在字符串缓冲区被多个线程同时使用时,也就是多线程同时操作,这样会有出现错误操作的概率,为了保证线程的安全性,进行加锁,这样会使同一时间只有一个线程获得权限,其他线程必须等待该线程结束并释放锁才能获得权限,这样线程非常安全,虽然效率慢了点,但是当项目安全性要求很高时就必须用 StringBuffer 。
单线程下还是用 StringBuilder 更快一些。
评论前必须登录!
注册