String、StringBuffer和StringBuilder之间的区别
@author 无忧少年
@createTime 2019/12/09
1. Java String 类——String字符串常量
字符串广泛应用 在Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。
需要注意的是,String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。我们来看一下这张对String操作时内存变化的图:
我们可以看到,初始String值为“hello”,然后在这个字符串后面加上新的字符串“world”,这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了“hello world”字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费。为了应对经常性的字符串相关的操作,谷歌引入了两个新的类——StringBuffer类和StringBuild类来对此种变化字符串进行处理。
2.Java StringBuffer 和 StringBuilder 类——StringBuffer字符串变量、StringBuilder字符串变量
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
3.这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。
3.1、首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String
String最慢的原因:String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。以下面一段代码为例:
String str="abc";
System.out.println(str);
str=str+"de";
System.out.println(str);
运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。
而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。
另外,有时候我们会这样对字符串进行赋值
String str="abc"+"de";
StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");
System.out.println(str);
System.out.println(stringBuilder.toString());
这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和String str=“abcde”;是完全一样的,所以会很快,而如果写成下面这种形式
String str1="abc";
String str2="de";
String str=str1+str2;
那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。
public static void main(String[] args) {
long a = new Date().getTime();
String cc = "";
int n = 10000;
for (int i = 0; i < n; i++) {
cc += "." + i;
}
System.out.println("String使用的时间" + (System.currentTimeMillis() - a) / 1000.0 + "s");
long s1 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append("." + i);
}
System.out.println("StringBuilder使用的时间" + (System.currentTimeMillis() - s1) / 1000.0 + "s");
long s2 = System.currentTimeMillis();
StringBuffer sbf = new StringBuffer();
for (int i = 0; i < n; i++) {
sbf.append("." + i);
}
System.out.println("StringBuffer使用的时间" + (System.currentTimeMillis() - s2) / 1000.0 + "s");
}
3.2. 再来说线程安全
在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的
如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。
(一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞)
3.3. 总结一下
String:适用于少量的字符串操作的情况
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况**
4. 这三种对象的比较方法
public static void main(String[] args) {
String str = "1";
String str1 = "1";
StringBuffer sbf = new StringBuffer("1");
StringBuffer sbf1 = new StringBuffer("1");
StringBuilder sbd = new StringBuilder("1");
StringBuilder sbd1 = new StringBuilder("1");
System.out.println("---- euqals 比较 ----");
boolean stringEqualString = str.equals(str1);
boolean stringBuilderEqualStringBuilder = sbd.equals(sbd1);
boolean stringBufferEqualStringBuffer = sbf.equals(sbf1);
System.out.println("String和String比较结果:" + stringEqualString);
System.out.println("StringBuilder和StringBuilder比较结果:" + stringBuilderEqualStringBuilder);
System.out.println("StringBuffer和StringBuffer比较结果:" + stringBufferEqualStringBuffer);
boolean stringEqualStringBuffer = str.equals(sbf);
boolean stringEqualStringBuilder = str.equals(sbd);
boolean stringBufferEqualStringBuilder = sbf.equals(sbd);
System.out.println("String和StringBuffer比较结果:" + stringEqualStringBuffer);
System.out.println("String和StringBuilder比较结果:" + stringEqualStringBuilder);
System.out.println("StringBuffer和StringBuilder比较结果:" + stringBufferEqualStringBuilder);
System.out.println("---- == 比较 ----");
boolean stringEqualString1 = str == str1;
boolean stringBuilderEqualStringBuilder1 = sbd == sbd1;
boolean stringBufferEqualStringBuffer1 = sbf == sbf1;
System.out.println("String和String比较结果:" + stringEqualString1);
System.out.println("StringBuilder和StringBuilder比较结果:" + stringBuilderEqualStringBuilder1);
System.out.println("StringBuffer和StringBuffer比较结果:" + stringBufferEqualStringBuffer1);
}
}
// 结果为
---- euqals 比较 ----
String和String比较结果:true
StringBuilder和StringBuilder比较结果:false
StringBuffer和StringBuffer比较结果:false
String和StringBuffer比较结果:false
String和StringBuilder比较结果:false
StringBuffer和StringBuilder比较结果:false
---- == 比较 ----
String和String比较结果:true
StringBuilder和StringBuilder比较结果:false
StringBuffer和StringBuffer比较结果:false
结果可以看到只有String和String 的equal方法是相等的,说明相同值的情况下只有String是指向同一个对象的,StringBuffer和StringBuilder都是新创建的对象。 ==比较也是如此