String是不可变类,String对象都是常量,就像“ABC”、“SDF”和“字符串123”等都是存储在常量池中。所以对String进行任何更改操作都会产生新的String对象。而StringBuilder与StringBuffer 是可变类,它们的对象是一个变量,对StringBuilder和StringBuffer对象的操作不会生成新的对象,依然是原来的对象。可以理解为String类是不可变字符串,而StringBuilder 和StringBuffer是可变字符串。
String与StringBuilder、StringBuffer创建字符串的方式:
String s1 = "字符串";
String s2 = new String("字符串");
StringBuilder sbi = new StringBuilder("字符串");
StringBuffer sbu = new StringBuffer("字符串");
代码可以看出String类可以使用两种方式来创建,但通常推荐第一种,因为只会生成一个String对象,而采用newString(“字符串”)的话会生成两个String对象,开销比第一种大。但遇到需要将指定类型数组转成字符串时,可以用new String()的重载构造器轻松完成,所以根据具体情况来使用。StringBuilder和StringBuffer 是可变的,只能采用new StringBuilder() 和 new StringBuffer()来创建对应的对象。
System.out.println(s1.equals(s2)); //true
System.out.println(sbi.equals(sbu)); //false
因为String类重写了equals(),调用equals()是比较s1和s2的内容,所以返回true。而StringBuilder和StringBuffer的equals()并未重写,和==操作符一样只能比较它们的内存地址是否一致,不能比较其内存空间的内容。所以返回false。
测试StringBuffer和StringBuilder对象的哈希值:
StringBuilder sbd = new StringBuilder("字符串");
System.out.println("sbd的hashcode():" + sbd.hashCode());
sbd = new StringBuilder("字符串"); //sbd重新指向一个new StringBuilder("字符串")
System.out.println("sbd的hashcode():" + sbd.hashCode());
StringBuffer sbu = new StringBuffer("字符串");
System.out.println("sbu的hashcode():" +sbu.hashCode());
sbu = new StringBuffer("字符串"); //sbu重新指向一个new StringBuffer("字符串")
System.out.println("sbu的hashcode():" +sbu.hashCode());
// sbd的hashcode():366712642
// sbd的hashcode():1829164700
// sbu的hashcode():2018699554
// sbu的hashcode():1311053135
虽然重新指向的依然是new StringBuilder("字符串"),但输出的哈希值都被改变了,说明了StringBuilder或者StringBuffer只要new 之后,就会产生一个新对象,对象在堆中是唯一,所以它们的内存地址都是不同的,它们的equals()只能比较内存地址,所以不相等。
StringBuilder 和StringBuffer的区别:
StringBuilder没有实现线程同步,是非线程安全的。不支持并发。
StringBuffer实现了线程同步,是线程安全的,支持并发。
StringBuffer类的方法使用了synchronized来进行修饰,而StringBuilder的方法都未使用。
贴上一段它们内部的源代码:
//StingBuffer
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
@Override
public synchronized StringBuffer insert(int offset, char[] str) {
toStringCache = null;
super.insert(offset, str);
return this;
}
//StringBuilder
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
@Override
public StringBuilder deleteCharAt(int index) {
super.deleteCharAt(index);
return this;
}
关于synchronized关键字,简单说一下。在Java中,synchronized表示同步,方法被修饰后就是同步方法,而同步锁就是调用该方法的当前对象,所以修饰方法无需显式指定同步锁对象。synchronized也可以作为一个代码块,把需要同步的代码放入其中,但需要显式指定同步锁对象。除了synchronized,我们还可以使用Lock来进行同步加锁,这里只是复习一下。
StringBuilder和StringBuffer使用场景:
两者的方法都是一样的,本质上是同步与不同步的区别,所以我们根据线程环境来确定使用哪一个。
在单线程环境下,无需担心线程安全问题,优先使用StringBuilder,因为效率高于StringBuffer。
在多线程环境下,则优先使用StringBuffer,保证线程安全。这会导致效率会低一点。
使用范例:
在JDBC编程中,会大量使用拼接字符串。如果我们使用String,每当使用一次,就会产生一个新String对象。当常量池中的无引用String对象越来越多时,JVM就会通知gc进行回收,这会导致我们的程序效率变低。而使用StringBuilder或StringBuffer的话只需要一个对象,大大节省了内存空间。
在下面代码中,同样是将数组转成字符串,但采用StringBuilder和String两种形式,毫无疑问是StringBuilder更加效率。需要使用拼接字符串的时候,StringBuilder和StringBuffer会更具有优势。
//使用StringBuilder将一个数组转成字符串
public static String arrToSB(int[] arr) {
StringBuilder sb = new StringBuilder();
sb.append("【"); //附加【字符串
//遍历数组元素,添加到可变字符串sb中
for(int i = 0; i < arr.length; i++) {
sb.append(arr[i] + ",");
if(i == arr.length - 1) {
sb.append(arr[i] + "】");
}
}
return sb.toString();
}
//使用String将一个数组转成字符串
public static String arrToString(int[] arr) {
String s = ("{"); //附加【字符串
//遍历数组元素,添加到可变字符串sb中
for(int i = 0; i < arr.length; i++) {
s = s + (arr[i] + ",");
System.out.println(s); //打印添加的过程
if(i == arr.length - 1) {
s = s + (arr[i] + "}");
}
}
return s;
}