它们之间的区别:
在我们学习String类的时候,也会学习到StringBuilder和StringBuffer,但是他们之间有什么区别呢? 当然他们在具体的代码实现上、内存分配上以及效率上都有着不同(我这里以JDK8为例);
一、代码实现
String
public final class Stringimplements 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/** use serialVersionUID from JDK 1.0.2 for interoperability */private static final long serialVersionUID = -6849794470754667710L;/*** Class String is special cased within the Serialization Stream Protocol.** A String instance is written into an ObjectOutputStream according to* <a href="{@docRoot}/../platform/serialization/spec/output.html">* Object Serialization Specification, Section 6.2, "Stream Elements"</a>*/private static final ObjectStreamField[] serialPersistentFields =new ObjectStreamField[0];/*** Initializes a newly created {@code String} object so that it represents* an empty character sequence. Note that use of this constructor is* unnecessary since Strings are immutable.*/public String() {this.value = "".value;}/*** Initializes a newly created {@code String} object so that it represents* the same sequence of characters as the argument; in other words, the* newly created string is a copy of the argument string. Unless an* explicit copy of {@code original} is needed, use of this constructor is* unnecessary since Strings are immutable.** @param original* A {@code String}*/public String(String original) {this.value = original.value;this.hash = original.hash;}
从这里我们可以看出,String的底层结构是private final char value[];所以String的值是不可变的,并且实现了Comparable接口,所以是支持比较的。
StringBuilder
public final class StringBuilderextends AbstractStringBuilderimplements java.io.Serializable, CharSequence
{/** use serialVersionUID for interoperability */static final long serialVersionUID = 4383685877147921099L;/*** Constructs a string builder with no characters in it and an* initial capacity of 16 characters.*/public StringBuilder() {super(16);}/*** Constructs a string builder with no characters in it and an* initial capacity specified by the {@code capacity} argument.** @param capacity the initial capacity.* @throws NegativeArraySizeException if the {@code capacity}* argument is less than {@code 0}.*/public StringBuilder(int capacity) {super(capacity);}/*** Constructs a string builder initialized to the contents of the* specified string. The initial capacity of the string builder is* {@code 16} plus the length of the string argument.** @param str the initial contents of the buffer.*/public StringBuilder(String str) {super(str.length() + 16);append(str);}/*** Constructs a string builder that contains the same characters* as the specified {@code CharSequence}. The initial capacity of* the string builder is {@code 16} plus the length of the* {@code CharSequence} argument.** @param seq the sequence to copy.*/public StringBuilder(CharSequence seq) {this(seq.length() + 16);append(seq);}@Overridepublic StringBuilder append(Object obj) {return append(String.valueOf(obj));}@Overridepublic StringBuilder append(String str) {super.append(str);return this;}
从这里我们可以看出StringBuilder有一个抽象父类,他是没有实现Comparable接口,明显不支持比较。我们看到AbstractStringBuilder类中,value是可变的,并不像String有final修饰
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;/*** This no-arg constructor is necessary for serialization of subclasses.*/AbstractStringBuilder() {}
StringBuffer
public final class StringBufferextends AbstractStringBuilderimplements java.io.Serializable, CharSequence
{/*** A cache of the last value returned by toString. Cleared* whenever the StringBuffer is modified.*/private transient char[] toStringCache;/** use serialVersionUID from JDK 1.0.2 for interoperability */static final long serialVersionUID = 3388685877147921107L;/*** Constructs a string buffer with no characters in it and an* initial capacity of 16 characters.*/public StringBuffer() {super(16);}/*** Constructs a string buffer with no characters in it and* the specified initial capacity.** @param capacity the initial capacity.* @exception NegativeArraySizeException if the {@code capacity}* argument is less than {@code 0}.*/public StringBuffer(int capacity) {super(capacity);}/*** Constructs a string buffer initialized to the contents of the* specified string. The initial capacity of the string buffer is* {@code 16} plus the length of the string argument.** @param str the initial contents of the buffer.*/public StringBuffer(String str) {super(str.length() + 16);append(str);}/*** Constructs a string buffer that contains the same characters* as the specified {@code CharSequence}. The initial capacity of* the string buffer is {@code 16} plus the length of the* {@code CharSequence} argument.* <p>* If the length of the specified {@code CharSequence} is* less than or equal to zero, then an empty buffer of capacity* {@code 16} is returned.** @param seq the sequence to copy.* @since 1.5*/public StringBuffer(CharSequence seq) {this(seq.length() + 16);append(seq);}@Overridepublic synchronized int length() {return count;}@Overridepublic synchronized int capacity() {return value.length;}
而StringBuffer同样是继承一个抽象父类AbstractStringBuilder,它的底层结构同样是可变的char[] value数组也没有实现Comparable接口。
StringBuffer和StringBuilder虽然结构是一样的,但还是有不同点。StringBuffer多了几样东西。
如:
1、toStringCache它是用于执行toString()方法时,把值保存到变量中,下次继续执行toString()方法时,可以直接从变量中取出来。变量是transient 修饰的,代表着不可序列化。
2、还有一个本质上的区别,StringBuffer的方法是synchronized修饰的,代表着是线程安全的。
二、性能效率
String
// StirngString str = "";long oldTime = System.currentTimeMillis();for (int i = 0; i < 100000; i++) {str += "a";}System.out.printf("耗时:%d ms",(System.currentTimeMillis() - oldTime));
我这里使用for循环,每循环一次,都往str中拼接字符串"a",总共循环10万次。执行时间达到了平均11秒,太可怕了,效率实在太慢太慢了。
StringBuilder
// StringBuilderStringBuilder str = new StringBuilder("");long oldTime = System.currentTimeMillis();for (int i = 0; i < 10000000; i++) {str.append("a");}System.out.printf("耗时:%d ms",(System.currentTimeMillis() - oldTime));
我这里使用StringBuilder的append方法进行拼接字符串,拼接1000万次,平均执行时间只需279毫秒,这里对比String可以看出,在大量拼接字符串的时候,String的劣势很明显。
当然StringBuilder虽然够快,但也有他的劣势,在多线程的环境下是线程不安全的。
StringBuffer
// StringBufferStringBuffer str = new StringBuffer("");long oldTime = System.currentTimeMillis();for (int i = 0; i < 10000000; i++) {str.append("a");}System.out.printf("耗时:%d ms",(System.currentTimeMillis() - oldTime));
这里同样使用StringBuffer拼接字符串,同样1000万次,耗时平均550毫秒。虽然它没有StringBuilder快,但是它可以保证线程安全,我们前面讲过StringBuffer的方法是synchronized修饰的。当然线程安全就代表着它性能效率会被降低了,因为我们的线程需要去竞争锁,竞争到锁的线程才可以执行,虽然有偏向锁,性能难免会被降低。
三、内存分配
JVM内存区域划分如下(自画,如果有觉得错的地方,可以私信我修正哈)
String
String str = “a” + “b” + “c”; //我们以这段代码为例,我们来看看内存中是如何分配的
【总所周知,字符串常量池在JDK7中从方法区搬到了堆中!】
(字面量:字符串常量池中带"“双引号的字,如"a”、“b”、"c"都是字面量)
我们可以看到,在使用String进行拼接不同字符串的时候,每次拼接相连,都会在常量池中创建相应的字面量。这是因为String的value是final修饰的,并不能直接修改,所以会一直创建新的对象,并重新进行赋值。
在JDK9中,String底层是“byte[]”,并不是“char[]”了,这样做的目的就是可以通过编码的方式来存储,可以减少内存的占用和提高性能,我测试过性能快了不止一倍。