JavaStringStringBuilder和StringBuffer的区别
Posted remo0x
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaStringStringBuilder和StringBuffer的区别相关的知识,希望对你有一定的参考价值。
三者关系
String实现了Serializable、Comparable、CharSequence接口
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
StringBuilder继承了AbstractStringBuilder类,实现了Serializable、CharSequence接口
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
StringBuffer继承了AbstractStringBuilder类,实现了Serializable、CharSequence接口
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
三者的继承关系如下所示
Object
|——>String
|——>AbstractStringBuilder
|——>StringBuilder
|——>StringBuffer
StringBuilder和StringBuffer的关系最近,而String是单独实现的。StringBuilder和StringBuffer中的方法几乎都是调用AbstractStringBuilder中的方法实现的,它们有同样的方法,只是StringBuffer在方法上加上了synchronized修饰。StringBuilder用于单线程,StringBuffer用于多线程,而String由于是final的所以也是线程安全的
由于StringBuilder和StringBuffer几乎只是代理了AbstractStringBuilder中的数据和方法,所以只需要分析AbstractStringBuilder就行了
CharSequence
三者都实现了CharSequence接口,所以有一些共同的方法。主要的方法如下所示
方法 | 描述 |
---|---|
int length() | 返回字符序列的长度,按2字节char计算 |
char charAt(int index) | 返回指定索引号的char |
CharSequence subSequence(int start,int end) | 返回子字符序列,长度为(end-start) |
常用的也就length()和charAt(),而这两个方法都是CharSequence中声明的
底层实现
String是用final字符数组实现的,所以value.length
就是真实数据的长度,也是length()的返回值。而String的hash值也因此相当于是固定的,所以可以缓存在变量中
private final char value[];
private int hash;
AbstractStringBuilder是用可变字符数组实现的,所以需要用一个count记录数组中的真实数据
char[] value;
int count;
StringBuffer中比StringBuilder多出了一个字段toStringCache
,只要修改了value就更新该字段
private transient char[] toStringCache;
而该字段只在toString()中使用
@Override
public synchronized String toString()
if (toStringCache == null)
toStringCache = Arrays.copyOfRange(value, 0, count);
return new String(toStringCache, true);
toString()调用的String的构造函数如下
String(char[] value, boolean share)
// assert share : "unshared not supported";
this.value = value;
我猜测增加这个字段应该是这样考虑的
- StringBuffer用于多线程,所以增加一个toString()的缓存可以减少调用
Arrays.copyOfRange()
的次数,减少了复制开销 - 在toString()中调用的String的构造函数是共享toStringCache生成的String对象,减少了复制操作而增加了执行速度。由于String不可修改,所以不能由该String对象影响toStringCache的值,这是安全的
构造函数
String的构造函数的基本思路都是复制源char数组生成一个新的char数组,如果不提供源数组则新建一个char[0]数组,比较典型的如下
public String()
this.value = new char[0];
public String(String original)
this.value = original.value;
this.hash = original.hash;
public String(char value[])
this.value = Arrays.copyOf(value, value.length);
AbstractStringBuilder只有一个构造函数,使用指定的capacity新建数组
AbstractStringBuilder(int capacity)
value = new char[capacity];
在StringBuilder和StringBuffer中会调用AbstractStringBuilder的构造函数新建对象,基本思路是使用指定的capacity新建数组,如果没有提供capacity则使用16
,如果提供了源char数组则将容量扩大16。比如StringBuilder的构造函数
public StringBuilder()
super(16);
public StringBuilder(int capacity)
super(capacity);
public StringBuilder(String str)
super(str.length() + 16);
append(str);
equals和hashCode()
AbstractStringBuilder是可变的,所以没有提供equals方法和hashCode(),也没有实现Comparable接口。如果要比较AbstractStringBuilder,需要调用toString()将其转为String对象再进行比较操作
String的equals方法是比较value中的字符是否相等
public boolean equals(Object anObject)
if (this == anObject)
return true;
if (anObject instanceof String)
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length)
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0)
if (v1[i] != v2[i])
return false;
i++;
return true;
return false;
equals方法判断相等的步骤是:
- 若指向同一个内存地址,即为同一个String实例,返回true
- 如果对象不是String实例,返回false
- 如果value长度不一样,返回false
- 逐个字符比较,若有不相等字符返回false
- 所有字符都相同,返回true
String的hashCode()是根据value生成hash值,这样不同value的字符串就容易生成不同的hash值,既能减少碰撞又能生成与内容相关的hash值
public int hashCode()
int h = hash;
if (h == 0 && value.length > 0)
char val[] = value;
for (int i = 0; i < value.length; i++)
h = 31 * h + val[i];
hash = h;
return h;
实现的计算表达式如下所示(s[i]是字符串的第i个字符,n是字符串的长度,^表示求幂。空字符串的hash值是0):
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
对于乘子31的选择,主要有两点原因:
- 31是一个不大不小的质数,是作为hashCode()乘子的优选质数之一,可以均匀分布hash值并减少信息丢失
- 31可以被JVM优化,31 * i = (i << 5) - i
String方法
String不可变,所以没有提供append方法和insert方法,某些看起来是修改String的方法其实都是new了一个新的String返回。比如replace方法
public String replace(char oldChar, char newChar)
if (oldChar != newChar)
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
// 找到第一个指向oldChar的i
while (++i < len)
if (val[i] == oldChar)
break;
if (i < len)
char buf[] = new char[len];
// 将i之前的char复制到buf中
for (int j = 0; j < i; j++)
buf[j] = val[j];
// 将所有oldChar换成newChar
while (i < len)
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
return new String(buf, true);
return this;
AbstractStringBuilder方法
AbstractStringBuilder主要是提供了append方法和insert方法及其重载,用于便捷的修改char数组,最终都是在value上进行操作
比如append(String str)
public AbstractStringBuilder append(String str)
if (str == null)
return appendNull();
int len = str.length();
// 先扩展value,再copy进去,这样可以返回this
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
还有insert方法
public AbstractStringBuilder insert(int index, char[] str, int offset,
int len)
if ((index < 0) || (index > length()))
throw new StringIndexOutOfBoundsException(index);
if ((offset < 0) || (len < 0) || (offset > str.length - len))
throw new StringIndexOutOfBoundsException(
"offset " + offset + ", len " + len + ", str.length "
+ str.length);
ensureCapacityInternal(count + len);
System.arraycopy(value, index, value, index + len, count - index);
System.arraycopy(str, offset, value, index, len);
count += len;
return this;
AbstractStringBuilder可以自动扩展value的大小,由下面的几个方法实现
public void ensureCapacity(int minimumCapacity)
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
private void ensureCapacityInternal(int minimumCapacity)
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
void expandCapacity(int minimumCapacity)
int newCapacity = value.length * 2 + 2;
// 取两者最大值
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0)
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
// (value.length * 2 + 2)溢出
newCapacity = Integer.MAX_VALUE;
value = Arrays.copyOf(value, newCapacity);
执行速度
理论上讲,三者的执行速度如下
String < StringBuffer < StringBuilder
因为String不可变所以对它的操作会产生一个新的String对象,所以速度最慢。而StringBuffer在StringBuilder的基础上加上了synchronized,所以速度比StringBuilder慢
但是如果把StringBuffer用于单线程中,那JVM可能会删除synchronized修饰用于性能调优,这时候StringBuilder和StringBuffer几乎是一样的
在某些特殊情况下, String对象的字符串拼接被JVM编译成StringBuilder对象的拼接,所以这些时候String对象的速度并不会比StringBuffer对象慢,比如
String a = "1" + "2" + "3";
# 编译成
String a = new StringBuilder("1").append("2").append("3").toString();
参考文章
- String,StringBuffer,StringBuilder的区别及其源码分析(一)
- String、StringBuffer与StringBuilder之间区别
- 科普:为什么 String hashCode 方法选择数字31作为乘子
以上是关于JavaStringStringBuilder和StringBuffer的区别的主要内容,如果未能解决你的问题,请参考以下文章
tokenize($s) 和 tokenize($s, ' ') 一样吗?