String、StringBuilder、StringBuffer
1 三者区别
-
字符修改上的区别:String是不可变字符序列,StringBuilder和StringBuffer是可变字符序列
-
线程安全上的区别:StringBuffer线程安全,String和StringBuilder线程不安全
-
初始化上的区别,String可以空赋值,后者不行,报错
//String类型可以赋空值: String s = null; String s = “abc”;
//StringBuilder和StringBuffer不能赋空值,但可以创建空对象: StringBuffer s = null; //结果警告:Null pointer access: The variable result can only be null at this location StringBuffer s = new StringBuffer();//StringBuffer对象是一个空的对象 StringBuffer s = new StringBuffer(“abc”);//创建带有内容的StringBuffer对象,对象的内容就是字符串”
String的使用陷阱:
-
不可变
String s = "abc"; //创建一个字符串 s = s + "b"; //实际上原来的字符串对象"abc"已经被丢弃,又产生了一个新的字符串对象s + "b"(即"abcb")
一般情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下,在单线程使用字符缓冲区时推荐使用StringBuilder
一般情况下,StringBuffer的的效率是比String高的,但在以下情况下,String效率甚至比StringBuffer要高:
String S1 = "This is only a" + "simple" + "test";
StringBuffer Sb = new StringBuilder("This is only a").append("simple").append("test");
//在 JVM 眼里,这个String S1 = "This is only a" + "simple" + "test"; 其实就是:String S1 = "This is only a simple test";
String S2 = "This is only a";
String S3 = "simple";
String S4 = "test";
String S1 = S2 +S3 + S4;
//在这种情况下JVM会规规矩矩的按照原来的方式去做
2 创建String对象
//代码1
String sa=new String("Hello world");
String sb=new String("Hello world");
System.out.println(sa==sb); // false
//代码2
String sc="Hello world";
String sd="Hello world";
System.out.println(sc==sd); // true
代码1中局部变量sa,sb中存储的是JVM在堆中new出来的两个String对象的内存地址,虽然这两个String对象的值(char[]存放的字符序列)都是"Hello world", 但是"=="比较的是两个不同的堆地址,所以返回值是false。
代码2中局部变量sc,sd中存储的也是地址,但却都是常量池中"Hello world"指向的堆的唯一的那个拘留字符串对象
3 StringBuffer与String的可变性问题
//String
public final class String
{
private final char value[];
public String(String original) {
// 把原字符串original切分成字符数组并赋给value[];
}
}
//StringBuffer
public final class StringBuffer extends AbstractStringBuilder
{
char value[]; //继承了父类AbstractStringBuilder中的value[]
public StringBuffer(String str) {
super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组
append(str); //将str切分成字符序列并加入到value[]中
}
}
-
String和StringBuffer中的
value[]
都用于存储字符序列 -
String中的是常量(final)数组,只能被赋值一次
-
StringBuffer中的
value[]
就是一个很普通的数组,而且可以通过append()
方法将新字符串加入value[]
末尾,这样也就改变了value[]
的内容和大小 -
总结:讨论String和StringBuffer可不可变,本质上是指对象中的
value[]
字符数组可不可变,而不是对象引用可不可变对象本身指的是存放在堆空间中的该对象的实例数据(非静态非常量字段)。
对象引用指的是堆中对象本身所存放的地址.
一般方法区和Java栈中存储的都是对象引用,而非对象本身的数据。
4 StringBuffer与StringBuilder的线程安全性问题
在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized
修饰了,而StringBuilder没有。
注意:是不是String也不安全呢?事实上不存在这个问题,String是不可变的。线程对于堆中指定的一个String对象只能读取,无法修改。试问:还有什么不安全的呢?
5 String和StringBuffer的效率问题
StringBuffer和StringBuilder可谓双胞胎,StringBuilder是1.5新引入的,其前身就是StringBuffer。StringBuilder的效率比StringBuffer稍高,如果不考虑线程安全,StringBuilder应该是首选。另外,JVM运行程序主要的时间耗费是在创建对象和回收对象上。
-
String常量与String变量的"+"操作比较
String str=""; str="Heart"+"Raid"; //str="Heart"+"Raid"在编译阶段就连起来了,成为了字符串常量"HeartRaid",并指向堆中的拘留字符串对象。 //运行时只需要将"HeartRaid"指向的拘留字符串对象地址取出,存放在局部变量str中 String s1="Heart"; String s2="Raid"; String str=""; str=s1+s2; //s1和s2存放的是两个不同的拘留字符串对象的地址。然后会通过下面三个步骤完成“+连接”: //StringBuilder temp=new StringBuilder(s1), //temp.append(s2); //str=temp.toString();
-
String对象的"累+"连接操作与StringBuffer对象的append()累和连接操作比较
-
总结:
-
在编译阶段就能够确定的字符串常量,完全没有必要创建String或StringBuffer对象。直接使用字符串常量的"+"连接操作效率最高。
-
StringBuffer对象的append效率要高于String对象的"+"连接操作。
-
不停的创建对象是程序低效的一个重要原因。那么相同的字符串值能否在堆中只创建一个String对象那。显然拘留字符串能够做到这一点,除了程序中的字符串常量会被JVM自动创建拘留字符串之外,调用String的intern()方法也能做到这一点。当调用intern()时,如果常量池中已经有了当前String的值,那么返回这个常量指向拘留对象的地址。如果没有,则将String值加入常量池中,并创建一个新的拘留字符串对象。
-