String、StringBuilder、StringBuffer的区别
目录
概述
- 修改字符串速度
StringBuilder>StringBuffer>String - 内容是否可变
只有String不可变。
3.线程安全
只有StringBuilder是线程不安全的。 - 恳请各位补充指正。
1. 修改字符串速度
开门见山,以一段代码来比较三者的速度。
public class StingVelocity {
public static void stringTime() {
String str = "This is a string";
long begintime = System.currentTimeMillis();
for (int i = 1; i < 100000; i++) {
str = str + "a";
}
long endtime = System.currentTimeMillis();
long time = endtime - begintime;
System.out.println("string use time:" + time);
}
public static void stringBuilderTime() {
StringBuilder strbd = new StringBuilder("This is a StringBuilder");
long begintime = System.currentTimeMillis();
for (int i = 1; i < 100000; i++) {
strbd.append("a");
}
long endtime = System.currentTimeMillis();
long time = endtime - begintime;
System.out.println("stringBuilder use time:" + time);
}
public static void stringBufferTime() {
StringBuffer strbf = new StringBuffer("This is a stringbuffer");
long begintime = System.currentTimeMillis();
for (int i = 1; i < 100000; i++) {
strbf.append("a");
}
long endtime = System.currentTimeMillis();
long time = endtime - begintime;
System.out.println("stringBuffer use time:" + time);
}
public static void main(String[] args) {
stringTime();
stringBuilderTime();
stringBufferTime();
}
}
运行发现同样向字符串中添加100000个字符串”a”,String用时最为夸张,为2960ms,而StringBuilder和StringBuffer则相差无几分别为3ms和4ms。
每次运行大致有别,但整体用时趋势是String>StringBuffer>StringBuilder,原因在下面两部分中。
2.内容是否可变
判断内容是否可变时我们需要去看三个类的源码。
我们常说String是字符串常量,StringBuffer和StringBuilder是字符串变量,有人说是因为String被final修饰,所以我们称之为字符串常量。然而事实并非如此,如果去查看源码就会发现,这三个类都是被final修饰的。
复习一下final,final的三个作用:
1.修饰变量时,则变量值不能改变,即为常量。修饰常量时,常量往往全大写。
2.修饰方法时,则该方法不能重写。
3.修饰类时,该类不能被继承。
第三点说明问题了,String作为一个类,被final修饰,当然是不能被继承了,和它不能被改变有什么关系呢?因此,这不是它不开改变的原因,走进源码,我们去找真正不可变的原因。
首先查看String的,如下:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
可以看到源码中有两个属性值,value和hash,其中value是一个char数组,被final所修饰,没错,这是String是字符串常量的原因。value作为一个属性被final修饰符合final用法的第一条,因此value值不能改变,而String实际是由value值决定的,因此String值不能改变。
注:我这里说的String值不能改变太过绝对,在别人的博客中我发现可以通过反射机制reflct修改value的访问权限,进而修改value值,从而达到修改String值的效果。
因此,之前速度比较的程序中String的字符串的改变是通过不断地创建新对象来达成的,因此在10W次“+”操作中就产生了10W个新的字符串对象,这也是耗时远远大于StringBuffer和StringBuilder的原因。
看完string后,我们再看StringBuffer和StringBuilder的源码。
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
从上面两个源码片段可以看出来,两者都继承了AbstractStringBuilder,两者的源码中都没有对应于String的value值,那么value值去哪了呢?点进AbstractStringBuilder就会发现答案。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
原来value值在AbstractStringBuilder已经被定义了,它依然是一个字符数组,但是已经没有final值限制它了,所以他现在变成了变形金刚,可以随便变化了,这便是StringBuffer和StringBuilder能够变化的原因。也是String和这两个类中间的根本区别。
从这里
3.线程安全
我这里所提及的线程安全,是指在多线程环境中,当多个线程同时访问我们声明并赋值的String、StringBuffer、StringBuilder类对象时,是否会由于其发生变化而导致不同线程非同步访问时得到了其值,然后在修改它时被其他线程的修改结果所覆盖而无效。若不出现该情况,则证明该类是线程安全的,否则,则证明其为线程不安全的。后续会有实例验证。
三种字符串中,线程不安全的只有StringBuilder。
String作为不可变类,是明显线程安全的,Java中所有不可变类都是线程安全的。
StringBuffer类是可变类,但是StringBuffer类中实现的方法都是被Sychronized关键字所修饰的,因此它靠锁实现了线程安全。
Stringbuilder类是可变类,并且方法没有被Sychronized修饰,因此它是线程不安全的。
比较一下StringBuilder和StringBuffer的append方法的源码:
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
public StringBuilder append(String str) {
super.append(str);
return this;
}
果然是一个有synchronized,一个没有synchronized,没有锁,还安全吗?
接下来我通过编程实现了对StringBuilder的线程不安全的证明。流程如下:两个线程同时访问了StringBuilder类变量”abc”,一个线程的操作是向其中添加字符串“d”,另一个线程的操作是向其中添加字符串“e”。
提前给出两个猜测:
1.若StringBuilder线程安全,则在多线程情况下应当按我设定的线程顺序生成字符串“abcde”。
2.若线程不安全,则在多线程环境中根据两个线程以及主线程不同的运行顺序可能导致最后的StringBuilder类变量的值有以下五种可能:“abc”、“abcd”、“abce”、“abcde”、“abced”。
创建StringThreadSafe类如下:
public class StringThreadSafe {
public static void main(String[] args) throws InterruptedException {
StringBuilder str=new StringBuilder("abc");
Thread t1=new Thread(){
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
str.append("d");
}
};
Thread t2=new Thread(){
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
str.append("e");
}
};
t1.start();
t2.start();
Thread.sleep(100);
System.out.println(str.toString());
}
}
在3分钟以内多次运行程序得到了以下结果(中间出现过重复不过每种情况只截图一次)
1.abc的情况
2.abcd的情况
3.abce的情况
4.abcde的情况
5.abced的情况
这个结果证明了我们前面的第二种预测,即StringBuilder类是线程不安全的。
上述内容是在学习过程中通过对书本和网络上各路大佬的总结的阅读所整合得到的,整理下来加深一下印象。
如有错误,恳请指正。
如有缺漏,敬请补充。