在我们学习String类的时候,也会学习到StringBuilder和StringBuffer,但是他们之间有什么区别呢? 当然他们在具体的代码实现上、内存分配上以及效率上都有着不同(我这里以JDK8为例);
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
/** 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
*
* Object Serialization Specification, Section 6.2, "Stream Elements"
*/
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接口,所以是支持比较的。
public final class StringBuilder
extends AbstractStringBuilder
implements 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);
}
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public 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() {
}
public final class StringBuffer
extends AbstractStringBuilder
implements 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.
*
* 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);
}
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return value.length;
}
而StringBuffer同样是继承一个抽象父类AbstractStringBuilder,它的底层结构同样是可变的char[] value数组也没有实现Comparable接口。
StringBuffer和StringBuilder虽然结构是一样的,但还是有不同点。StringBuffer多了几样东西。
如:
1、toStringCache它是用于执行toString()方法时,把值保存到变量中,下次继续执行toString()方法时,可以直接从变量中取出来。变量是transient 修饰的,代表着不可序列化。
2、还有一个本质上的区别,StringBuffer的方法是synchronized修饰的,代表着是线程安全的。
// Stirng
String str = "";
long oldTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
str += "a";
}
System.out.printf("耗时:%d ms",(System.currentTimeMillis() - oldTime));
耗时:11114 ms
我这里使用for循环,每循环一次,都往str中拼接字符串"a",总共循环10万次。执行时间达到了平均11秒,太可怕了,效率实在太慢太慢了。
// StringBuilder
StringBuilder 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));
耗时:279 ms
我这里使用StringBuilder的append方法进行拼接字符串,拼接1000万次,平均执行时间只需279毫秒,这里对比String可以看出,在大量拼接字符串的时候,String的劣势很明显。
当然StringBuilder虽然够快,但也有他的劣势,在多线程的环境下是线程不安全的。
// StringBuffer
StringBuffer 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));
耗时:547 ms
这里同样使用StringBuffer拼接字符串,同样1000万次,耗时平均550毫秒。虽然它没有StringBuilder快,但是它可以保证线程安全,我们前面讲过StringBuffer的方法是synchronized修饰的。当然线程安全就代表着它性能效率会被降低了,因为我们的线程需要去竞争锁,竞争到锁的线程才可以执行,虽然有偏向锁,性能难免会被降低。
JVM内存区域划分如下(自画,如果有觉得错的地方,可以私信我修正哈)
String str = “a” + “b” + “c”; //我们以这段代码为例,我们来看看内存中是如何分配的
【总所周知,字符串常量池在JDK8中从方法区搬到了堆中!】
我们可以看到,在使用String进行拼接不同字符串的时候,每次拼接相连,都会在常量池中创建相应的字面量。这是因为String的value是final修饰的,并不能直接修改,所以会一直创建新的对象,并重新进行赋值。
StringBuilder和StringBuffer在内存分配上是一样的,只会改自身对象中的value值,不会在堆中重复创建新的字面量进行重新赋值。但拼接完需要调用toString()方法转成String类型,这时候才会在堆中创建一个"abc"字面量