項(xiàng)目開發(fā)中很多時(shí)候我們都需要拼接字符串雇锡,那如何才能高效的完成字符串拼接呢拼岳?
指定初始容量
先來(lái)看一下StringBuilder的源碼(JDK7)
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</code> argument.
*
* @param capacity the initial capacity.
* @throws NegativeArraySizeException if the <code>capacity</code>
* argument is less than <code>0</code>.
*/
public StringBuilder(int capacity) {
super(capacity);
}
}
StringBuilder的默認(rèn)構(gòu)造方法調(diào)用的是父類AbstractStringBuilder 中的AbstractStringBuilder(int capacity)構(gòu)造方法,如下:
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() {
}
/**
* Creates an AbstractStringBuilder of the specified capacity.
*/
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
}
StringBuilder的內(nèi)部有一個(gè)char[], 在調(diào)用StringBuilder的無(wú)參構(gòu)造方法時(shí)其內(nèi)部char[]的默認(rèn)長(zhǎng)度是16。當(dāng)我們調(diào)用StringBuilder的append方法時(shí),其實(shí)就是不斷的往char[]里填東西的過程俯萌。
public StringBuilder append(String str) {
super.append(str);
return this;
}
其中,super.append是調(diào)用AbstractStringBuilder 的append(String str)方法上枕,如下:
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
StringBuilder的擴(kuò)容和ArrayList有些類似咐熙,具體代碼如下:
/**
* This method has the same contract as ensureCapacity, but is
* never synchronized.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
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();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
StringBuilder默認(rèn)長(zhǎng)度是16,然后辨萍,如果要append第17個(gè)字符棋恼,怎么辦返弹?
答案是采用 Arrays.copyOf()成倍復(fù)制擴(kuò)容!
擴(kuò)容的性能代價(jià)是很嚴(yán)重的:一來(lái)有數(shù)組拷貝的成本爪飘,二來(lái)原來(lái)的char[]也白白浪費(fèi)了要被GC掉义起。可以想見师崎,一個(gè)129字符長(zhǎng)度的字符串默终,經(jīng)過了16,32犁罩,64, 128四次的復(fù)制和丟棄齐蔽,合共申請(qǐng)了496字符的數(shù)組,在高性能場(chǎng)景下床估,這幾乎不能忍含滴。
由此可見,合理設(shè)置一個(gè)初始值多重要丐巫。使用之前先仔細(xì)評(píng)估一下要保存的字符串最大長(zhǎng)度谈况。
復(fù)用StringBuilder
StringBuilder.setLength()方法只重置它的count指針,而char[]則會(huì)繼續(xù)重用递胧,源碼如下:
public void setLength(int newLength) {
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
ensureCapacityInternal(newLength);
if (count < newLength) {
for (; count < newLength; count++)
value[count] = '\0';
} else {
count = newLength;
}
}
toString()方法:
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
而toString()時(shí)會(huì)把當(dāng)前的count指針也作為參數(shù)傳給String的構(gòu)造函數(shù)碑韵,所以不用擔(dān)心把超過新內(nèi)容大小的舊內(nèi)容也傳進(jìn)去了《衅ⅲ可見泼诱,StringBuilder是完全可以被重用的。
具體示例如下:
String[] history_steps = ruleDetailInfo.getHistory_steps().split(",");
for (String step : history_steps){
sb.setLength(0);
sb.append(ruleDetailInfo.getBusiness_id()).append("\t").append(step);
results.add(sb.toString());
}
+ 與 StringBuilder的區(qū)別
String s = "hello" + user.getName();
這一行代碼經(jīng)過javac編譯后的效果赊锚,的確等價(jià)于使用StringBuilder,但沒有設(shè)定長(zhǎng)度屉栓。
String s = new StringBuilder().append(“hello”).append(user.getName());
但是舷蒲,如果像下面這樣:
String s = “hello ”;
// 中間插入了其他一些代碼
s = s + user.getName();
每一條語(yǔ)句,都會(huì)生成一個(gè)新的StringBuilder友多,這里就有了兩個(gè)StringBuilder牲平,性能就完全不一樣了。
如果是在循環(huán)體里s+=i; 就更加多得沒譜域滥,例如:
String str = "";
for(int i=0; i<10000;i++){
str += i;
}
StringBuffer 與 StringBuilder區(qū)別
StringBuffer的源碼如下(JDK7):
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
/** use serialVersionUID from JDK 1.0.2 for interoperability */
static final long serialVersionUID = 3388685877147921107L;
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
public synchronized int length() {
return count;
}
public synchronized int capacity() {
return value.length;
}
public synchronized void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
expandCapacity(minimumCapacity);
}
}
public synchronized void setLength(int newLength) {
super.setLength(newLength);
}
public synchronized StringBuffer append(Object obj) {
super.append(String.valueOf(obj));
return this;
}
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
......
}
StringBuilder源碼:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
/** use serialVersionUID for interoperability */
static final long serialVersionUID = 4383685877147921099L;
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
public StringBuilder append(String str) {
super.append(str);
return this;
}
// Appends the specified string builder to this sequence.
private StringBuilder append(StringBuilder sb) {
if (sb == null)
return append("null");
int len = sb.length();
int newcount = count + len;
if (newcount > value.length)
expandCapacity(newcount);
sb.getChars(0, len, value, count);
count = newcount;
return this;
}
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
......
}
StringBuffer與StringBuilder都是繼承于AbstractStringBuilder纵柿,唯一的區(qū)別就是StringBuffer的函數(shù)上都有synchronized關(guān)鍵字。
小結(jié)
StringBuilder是非線程安全的启绰,所以不能在多線程環(huán)境下共享使用昂儒。StringBuilder在使用的時(shí)候一定要指定其初始大小,另外委可,對(duì)性能要求比較高的場(chǎng)景下渊跋,可以考慮用一個(gè)ThreadLocal 緩存可重用的StringBuilder。