不可变类 - String
一如既往,在进行学习之前我们先看看相关的定义吧~下面引自 JavaDocs:
Strings are constant; their values cannot be changed after they are created
String 类是不可变的,String 对象的值在创建后不会发生改变。换句话说,我们平常对 String 对象的操作,实际上都是创建了一个新的 String 对象,将该对象的引用赋给我们的对象,而内存之中仍然存有原来的两个字符串。可能有点绕,大家可以看看下面的代码:
public class Demo {
public static void main(String[] args) {
String str;
String changeStr;
str = "chaos";
changeStr = "";
System.out.println("初始");
System.out.println(str);
System.out.println(changeStr);
changeStr = change(str);
System.out.println("改变后");
System.out.println(changeStr);
System.out.println(str);
}
private static String change(String str){
str = str + "change";
return str;
}
}
输出:
初始
chaos
改变后
chaoschange
chaos
我们可以看到,change() 方法并没有改变 test 的值。为什么呢?因为实际上,传给 change() 方法的是引用的一个拷贝,原对象并没有作为参数被方法修改。
所以 String 具有一个大坑:如果我们没有注意到 String 类的这个特性,将多个频繁发生改变的字符串的对象类型选为 String的话,内存一定会受此影响,从而影响系统性能;此外,当引用过多,还会出发垃圾回收机制,影响性能。
线程安全的 StringBuffer
A thread-safe, mutable sequence of characters. A string buffer is like a String, but can be modified.
StringBuffer 和 String 的最大区别在于,StringBuffer 是一个可变类,换句话说,StringBuffer 中的值不但可以改变,还不会产生 String 类的副作用(不断创建对象)。此外,StringBuffer 还是线程安全的。例如:
public class Demo {
public static void main(String[] args) {
String str;
StringBuffer sb = new StringBuffer();
StringBuffer changeSb = new StringBuffer();
str = "chaos";
sb.append(str);
System.out.println("初始");
System.out.println(sb.toString());
changeSb = sb;
changeSb.append("change");
System.out.println("改变后");
System.out.println(sb.toString());
System.out.println(changeSb.toString());
}
}
输出:
初始
chaos
改变后
chaoschange
chaoschange
所以如果我们的程序中如果存在需要频繁修改值的字符串,最好是使用 StringBuffer,因为除了刚刚提到的“线程安全”和“可变”两个特性以外,每个 StringBuffer 还具有一定的缓冲区容量,只有当字符串大小超过容量上限才会增加容量大小。
那么这样看下来,很多人会觉得 StringBuffer 很完美,以后就用它了!对于这样的同学我想说一句:且慢!!!事实上,StringBuffer 虽然看起来很完美,但它的线程安全实现机制是它最大的弱点,我们先来看看它是怎么实现线程安全的:
@Override
public synchronized StringBuffer append(boolean b) {
toStringCache = null;
super.append(b);
return this;
}
我们可以看到,StringBuffer 通过 synchronized 关键字为方法加上同步锁,从而实现线程安全。但如果我们 append 1w 次,那么程序就要负担 1w 次加锁解锁带来的时间消耗,影响了效率。此外,大部分情况下,我们都是在单线程情况下使用 String,不需要考虑到线程安全问题,这就使得 StringBuffer 的加锁/解锁过程为程序带来了不必要的开支。所以在 Effective Java 中就有这么一句话:
Java 1.5发行版本中增加了非同步 StringBuilder 类,代替了现在已经过时的 StringBuffer 类
非线程安全的 StringBuilder
事实上,StringBuilder 的内部实现和 StringBuffer 几乎一样,唯一的区别在于:StringBuilder 中去掉了 synchronized 关键字,失去了线程安全的特性。
我们刚刚也提到了,StringBuffer 的线程安全机制在大多数情况下都不必要,为系统带来的开销完全是不必要的,所以 Java 1.5 发行版本推出了 StringBuilder。
其他方面倒没什么区别,所以最佳方案应该是使用 StringBuilder,如果需要考虑线程安全问题,再考虑 StringBuffer,或者重写 StringBuilder 的相应方法实现线程安全。