【Java基础】String、StringBuilder、StringBuffer的区别

一、String

1.String类的特性

(1)String是个final类:所以String类是不可继承的。同样String对象的值是不可以被改变的。

(2)String对象的创建方法比较(原理比较):

在JVM加载运行class文件时,对于字节文件中出现的常量(符号引用、字符串字面量等)会在方法区的常量池中分类存放。

其中,源代码中出现过的字符串 字面量会保存在CONSTANT_String_info常量表中。然后,根据不同的创建方法,会有不同的内容分配,具体如下:

①.字符常量方式创建String变量:

String str1 = “string”;
String str2 = “string”;

上面创建了两个String引用,都指向同样内容的字符串。我们知道该类在加载时,字符串常量被存放在CONSTANT_String_info常量表中,并且同一字符串内容只会存放一次(我们称常量表中的字符串为“拘留字符串”)。故此,上面两个String引用都指向CONSTANT_String_info常量表中的同一地址(同一拘留字符串)。因此,这里str1==str2返回true。(注:这里没有创建String对象,因为没有在堆中分配内存)。
②.通过new关键字创建String对象:

String str1 = new String(“string”);
String str2 = new String(“string”);

类在加载时,首先把源代码中出现的字符串常量放在CONTANT_String_info常量表中,故此:上面两条语句中相同的“string”常量值放在了字符串常量表中的同一处。然后,在执行到这两条语句时,根据new关键字,在堆中分别创建String对象str1和str2,并且两对象在堆中保存值均为“string”。也就是说,此时JVM中有三个地方存有同一字符串值:常量表中的拘留字符串、str1对象的堆空间、str2对象的堆空间。因此,这里的str1==str2返回的是false。调用String类重写过的equals()方法比较两对象的值才返回true。
③通过intern()方法创建的String"对象":

String str1 = "string";
String str2 = new String(“string”).ntern();

intern()的作用:当常量池的字符串常量表中存在与“string”相同(内容相同)的字符串(拘留字符串)时,则直接返回这个拘留字符串的地址,赋给str2(此时没有创建新的对象);
如果常量池中没有与“string”相同的拘留字符串时,则把“string”添加到常量池中(称为 拘留字符串) ,供下一个intern()时做返回用,并在堆中创建String对象,然后把对象的引用返回(此时有新对象创建)。
因此,这里str1==str2返回true。
由此可见,intern()方法主要可以用来避免创建内容重复的String对象。
2.String中重载的“+”工作原理
用String类重载的+拼接字符串时,可以在+前后根其它数据类型,不一定是String类型。其它数据类型会自动“向高看齐”转化为String类型。
重载的+操作符,其实是创建一个StringBuffer或StringBuilder对象,用append()方法对字符传进行连接,最后调用toString()方法返回String字符串。


注意用+拼接字符串的两种情况:
①用+拼接字符串的两个字符串变量:

String str1=“str”;
String str2 = “ing”;
String str = str1+str2;
String str1_2 = "string";

解析:这里用+拼接的是两个字符串变量,所以首先会创建一个StringBuffer/StringBuilder,然后append(str1).append(str2),把str1和str2拼接起来,最后通过toString()生成一个新的String对象并把引用返回,赋值给str。所以,这里的str==str1_2结果是false。这里创建了新的String对象。

②用+拼接两个字符串字面量:

String str1 = “str"+“ing”;
String str2 = "string";

解析:用+拼接两个字符串字面量时,JVM会自动把这两个字面量的合并值作为一个完成的字符串常量值 ,保存到常量池的字符串常量表中。因此,这里str1==str2结果是true。这里没有创建新的String对象,只是把拼接结果作为拘留字符串的保留地址返回了而已。

③String中的concat(str)拼接字符串
与+可以拼接其他不同的数据类型,concat()只能把Stringl类型的内容拼接到调用者后面。
通过查看源码:

public String concat(String str) {      
	int otherLen = str.length();      
	if (otherLen == 0) {          
		return this;  
	}  
	char buf[] = new char[count + otherLen];  
	getChars(0, count, buf, 0);  
	str.getChars(0, otherLen, buf, count);  
	return new String(0, count + otherLen, buf);  
}  

我们可以看到,concat()拼接字符串的原理时创建一个新的char[]字符数组,把两个字符串转化为char后存在新数组中,最后用char[]创建一个新的String对象。

二、StringBuilder

1. StringBuilder是个非线程安全的final类,不能被继承。
2. StringBuilder的append()工作原理

public StringBuilder append(String str) {
        super.append(str);
        return this;
}

StringBuilder是调用了其父类的append()方法的。源码如下:

 public AbstractStringBuilder append(String str) {
        if (str == null) {
            str = "null";
        }
        int len = str.length();
        if (len == 0) {
            return this;
        }

        int newCount = count + len;//统计拼接后字符串长度
        if (newCount > value.length) {
            expandCapacity(newCount);//如果拼接结果大于所用的char[],则扩容
        }
       //getChars将拼接字符串赋值到char[]中
        str.getChars(0, len, value, count);
        count = newCount;//更新char[]所保存的字符串长度
        return this;
    }

可知,用StringBuilder拼接字符串时,其实是在底层创建了一个char[]数组,然后通过char[]把要拼接的字符串添加到char[]。最后通过toString()生产最终拼接结果时就是通过return new String(char[])实现的。

三、StringBuffer

1. StringBuffer是个线程安全的final类,不能被继承。
2. StringBuffer的append()工作原理

public synchronized StringBuffer append(String str) {
    super.append(str);
    return this;
}

可以看到,StringBuffer的append()也是调用父类AbstractStringBuilder的append()方法实现的,原理同StringBuilder。其唯一不同的地方在于,加了一个synchronized()方法,保证了线程同步。

四、三者性能比较与选用

1. 性能:一般来说是StringBuilder>StringBuffer>String
从上面四种(其实应该说是五种,+分为字符串常量的拼接和变量的拼接两种)字符串的拼接来看,除了字符串常量的拼接是返回拘留字符串的地址外,其他四种(str1+str2、str1.concat(str2)、builder.append(str1).append(str2)、buffer.append(str1).append(str2))都是使用了StringBuilder、或者说是StringBuilder的父类拼接方法来做的----创建一char[ ]数组,把需要拼接的内容先存进char[ ]数组,最后通过char数组创建新的String对象返回。
造成三者性能差别的主要原因是:
(1)用String的+累加拼接字符串变量时,每拼接一个就会创建一个StringBuilder,并通过append()方法拼接,然后返回一个新的String对象。然后再继续拼接下一个变量。这样就会导致重复创建StringBuilder对象,性能低下。
用concat()累计拼接时,则每两个字符串对象都会创建一个char[ ]进行内容拼接并返回一个新的String对象作为结果,重复调用concat()会导致重复创建char[ ]和新的String对象,性能低下。
(2)StringBuilder在调用toString()之前都不会创建拼接结果,并且底层的char[ ]数组会自动扩容,一直到拼接字符串全部存入char[ ]数组后,调用toString()时才会创建新的String对象并返回,这样就避免了重复创建,效率提高。
(3)StringBuffer则因为使用了synchronized对append()进行了加锁,所以性能稍微低于StringBuilder。
2. 不同情景下的使用
拼接两个字符串字面量,用+操作符
单线程下拼接两个字符串字变量面量,用StringBuilder
多线程下拼接两个字符串变量,用StringBuffer。

猜你喜欢

转载自blog.csdn.net/renjingjingya0429/article/details/88414487
今日推荐