小瓜牛漫谈 — String、StringBuffer、StringBuilder
任何一個(gè)系統(tǒng)在開發(fā)的過程中, 相信都不會(huì)缺少對(duì)字符串的處理。
在 java 語言中, 用來處理字符串的的類常用的有 3 個(gè):?String、StringBuffer、StringBuilder。
?
它們的異同點(diǎn):
1) 都是 final 類, 都不允許被繼承;
2) String 長(zhǎng)度是不可變的, StringBuffer、StringBuilder 長(zhǎng)度是可變的;
3) StringBuffer 是線程安全的, StringBuilder 不是線程安全的。
?
String 類已在上一篇隨筆?小瓜牛漫談 — String?中敘述過, 這里就不再贅述。本篇隨筆意在漫游 StringBuffer 與 StringBuilder。
其實(shí)現(xiàn)在網(wǎng)絡(luò)上談?wù)?String、StringBuffer、StringBuilder 的文章已經(jīng)多到不可勝數(shù)了。小瓜牛不才, 蝸行牛步, 慢了半個(gè)世紀(jì)。。。
StringBuilder 與 StringBuffer 支持的所有操作基本上是一致的, 不同的是, StringBuilder 不需要執(zhí)行同步。同步操作意味著
要耗費(fèi)系統(tǒng)的一些額外的開銷, 或時(shí)間, 或空間, 或資源等, 甚至可能會(huì)造成死鎖。從理論上來講,?StringBuilder 的速度要更快一些。
?
串聯(lián)字符串的性能小測(cè):
1 public class Application { 2 3 private final int LOOP_TIMES = 200000; 4 private final String CONSTANT_STRING = "min-snail"; 5 6 public static void main(String[] args) { 7 8 new Application().startup(); 9 } 10 11 public void testString(){ 12 String string = ""; 13 long beginTime = System.currentTimeMillis(); 14 for(int i = 0; i < LOOP_TIMES; i++){ 15 string += CONSTANT_STRING; 16 } 17 long endTime = System.currentTimeMillis(); 18 System.out.print("String : " + (endTime - beginTime) + "\t"); 19 } 20 21 public void testStringBuffer(){ 22 StringBuffer buffer = new StringBuffer(); 23 long beginTime = System.currentTimeMillis(); 24 for(int i = 0; i < LOOP_TIMES; i++){ 25 buffer.append(CONSTANT_STRING); 26 } 27 buffer.toString(); 28 long endTime = System.currentTimeMillis(); 29 System.out.print("StringBuffer : " + (endTime - beginTime) + "\t"); 30 } 31 32 public void testStringBuilder(){ 33 StringBuilder builder = new StringBuilder(); 34 long beginTime = System.currentTimeMillis(); 35 for(int i = 0; i < LOOP_TIMES; i++){ 36 builder.append(CONSTANT_STRING); 37 } 38 builder.toString(); 39 long endTime = System.currentTimeMillis(); 40 System.out.print("StringBuilder : " + (endTime - beginTime) + "\t"); 41 } 42 43 public void startup(){ 44 for(int i = 0; i < 6; i++){ 45 System.out.print("The " + i + " [\t "); 46 testString(); 47 testStringBuffer(); 48 testStringBuilder(); 49 System.out.println("]"); 50 } 51 } 52 }上面示例是頻繁的去串聯(lián)一個(gè)比較短的字符串, 然后反復(fù)調(diào) 6 次。測(cè)試是一個(gè)很漫長(zhǎng)的過程, 在本人的筆記本電腦上總共花去了 23 分鐘之多, 下面附上具體數(shù)據(jù):
| Number | String | StringBuffer | StringBuilder |
| 0 | 231232 | 17 | 14 |
| 1 | 233207 | 6 | 6 |
| 2 | 231294 | 8 | 6 |
| 3 | 235481 | 7 | 6 |
| 4 | 231987 | 9 | 6 |
| 5 | 230132 | 8 | 7 |
| ?平均 | ?3'52'' | ?9.2 | ?7.5 |
?
?
?
?
?
?
?
?
?
從表格數(shù)據(jù)可以看出, 使用 String 的 "+" 符號(hào)串聯(lián)字符串的性能差的驚人, 大概會(huì)維持在 3分40秒 的時(shí)候可以看到一次打印結(jié)果;
其次是 StringBuffer, 平均花時(shí) 9.2 毫秒; 然后是 StringBuilder, 平均花時(shí) 7.5 毫秒。
?
1) 耗時(shí)大的驚人的 String 到底是干嘛去了呢? 調(diào)出 cmd 窗口, 敲 jconsole 調(diào)出 java 虛擬機(jī)監(jiān)控工具, 查看堆內(nèi)存的使用情況如下:
實(shí)際上這個(gè)已經(jīng)在上一篇?小瓜牛漫談?— String?中提到過, 底層實(shí)際上是將循環(huán)體內(nèi)的 string += CONSTANT_STRING; 語句轉(zhuǎn)成了:
string = (new StringBuilder(String.valueOf(string))).append("min-snail").toString();
所以在二十萬次的串聯(lián)字符串中, 每一次都先去創(chuàng)建 StringBuilder 對(duì)象, 然后再調(diào) append() 方法來完成 String 類的 "+" 操作。
這里的大部分時(shí)間都花在了對(duì)象的創(chuàng)建上, 而且每個(gè)創(chuàng)建出來的對(duì)象的生命都不能長(zhǎng)久, 朝生夕滅, 因?yàn)檫@些對(duì)象創(chuàng)建出來之后沒有引用變量來引用它們,
那么它們?cè)谑褂猛瓿蓵r(shí)候就處于一種不可到達(dá)狀態(tài), java 虛擬機(jī)的垃圾回收器(GC)就會(huì)不定期的來回收這些垃圾對(duì)象。因此會(huì)看到上圖堆內(nèi)存中的曲線起伏變化很大。
?
但如果是遇到如下情況:
1 String concat1 = "I" + " am " + "min-snail"; 2 3 String concat2 = "I"; 4 concat2 += " am "; 5 concat2 += "min-snail";java 對(duì) concat1 的處理速度也是快的驚人。本人在自己的筆記本上測(cè)試多次, 耗時(shí)基本上都是 0 毫秒。這是因?yàn)?concat1 在編譯期就可以被確定是一個(gè)字符常量。
當(dāng)編譯完成之后 concat1 的值其實(shí)就是?"I am min-snail", 因此, 在運(yùn)行期間自然就不需要花費(fèi)太多的時(shí)間來處理 concat1 了。如果是站在這個(gè)角度來看, 使用
StringBuilder 完全不占優(yōu)勢(shì), 在這種情況下, 如果是使用 StringBuilder 反而會(huì)使得程序運(yùn)行需要耗費(fèi)更多的時(shí)間。
但是 concat2 不一樣, 由于 concat2 在編譯期間不能夠被確定, 因此, 在運(yùn)行期間 JVM 會(huì)按老一套的做法, 將其轉(zhuǎn)換成使用 StringBuilder 來實(shí)現(xiàn)。
?
2) 從表格數(shù)據(jù)可以看出, StringBuilder 與 StringBuffer 在耗時(shí)上并不相差多少, 只是 StringBuilder 稍微快一些, 但是 StringBuilder 是
冒著多線程不安全的潛在風(fēng)險(xiǎn)。這也是 StringBuilder 為賺取表格數(shù)據(jù)中的 1.7 毫秒( 若按表格的數(shù)據(jù)來算, 性能已經(jīng)提升 20% 多 )所需要付出的代價(jià)。
?
3) 綜合來說:
StringBuilder 是 java 為 StringBuffer 提供的一個(gè)等價(jià)類, 但不保證同步。在不涉及多線程的操作情況下可以簡(jiǎn)易的替換 StringBuffer 來提升
系統(tǒng)性能; StringBuffer 在性能上稍略于 StringBuilder, 但可以不用考慮線程安全問題; String 的 "+"?符號(hào)操作起來簡(jiǎn)單方便,
String 的使用也很簡(jiǎn)單便捷, java 底層會(huì)轉(zhuǎn)換成 StringBuilder 來實(shí)現(xiàn), 特別如果是要在循環(huán)體內(nèi)使用, 建議選擇其余兩個(gè)。?
?
使用 StringBuffer、StringBuilder 的無參構(gòu)造器產(chǎn)生的對(duì)象默認(rèn)擁有 16 個(gè)字符長(zhǎng)度大小的字符串緩沖區(qū), 如果是調(diào)參數(shù)為 String 的構(gòu)造器,
默認(rèn)的字符串緩沖區(qū)容量是 String 對(duì)象的長(zhǎng)度 + 16 個(gè)長(zhǎng)度的大小(留 16 個(gè)長(zhǎng)度大小的空緩沖區(qū))。詳細(xì)信息可見 StringBuilder 源碼:
當(dāng)使用 append 或 insert 方法向源字符串追加內(nèi)容的時(shí)候, 如果內(nèi)部緩沖區(qū)的大小不夠, 就會(huì)自動(dòng)擴(kuò)張容量, 具體信息看 AbstractStringBuilder 源碼:
StringBuffer 與 StringBuilder 是相類似的, 這里就不貼 StringBuffer 的源碼了。
?
不同構(gòu)造器間的差異:
1 public static void main(String[] args) { 2 3 StringBuilder builder1 = new StringBuilder(""); 4 StringBuilder builder2 = new StringBuilder(10); 5 StringBuilder builder3 = new StringBuilder("min-snail"); // [ 9個(gè)字符 ] 6 7 System.out.println(builder1.length()); // 0 8 System.out.println(builder2.length()); // 0 9 System.out.println(builder3.length()); // 9 10 11 System.out.println(builder1.capacity()); // 16 12 System.out.println(builder2.capacity()); // 10 13 System.out.println(builder3.capacity()); // 25 [ 25 = 9 + 16 ] 14 15 builder2.append("I am min-snail"); // [ 14個(gè)字符 ] 16 17 System.out.println(builder2.length()); // 14 18 System.out.println(builder2.capacity()); // 22 [ 22 = (10 + 1) * 2 ] 19 }從上面的示例代碼可以看出, length() 方法計(jì)算的是字符串的實(shí)際長(zhǎng)度, 空字符串的長(zhǎng)度為 0 (這個(gè)和 String 是一樣的: "".length() == 0)。
capacity() 方法是用來計(jì)算對(duì)象字符串緩沖區(qū)的總?cè)萘看笮?
builder1 為: length + 16 = 0 + 16 = 16;
builder3 為: length + 16 = 9 + 16 = 25;
builder2 由于是直接指定字符串緩沖區(qū)的大小, 因此容量就是指定的值 10, 這個(gè)從源碼的構(gòu)造器中就能很容易的看出;
當(dāng)往 builder2 追加 14 個(gè)字符長(zhǎng)度大小的字符串時(shí), 這時(shí)候原有的緩沖區(qū)容量不夠用, 那么就會(huì)自動(dòng)的擴(kuò)容: (10 + 1) * 2 = 22
這個(gè)從源碼的 expandCapacity(int) 方法的第一行就能夠看的出。
?
不同構(gòu)造器的性能小測(cè):
1 public class Application { 2 3 private final int LOOP_TIMES = 1000000; 4 private final String CONSTANT_STRING = "min-snail"; 5 6 public static void main(String[] args) { 7 8 new Application().startup(); 9 } 10 11 public void testStringBuilder(){ 12 StringBuilder builder = new StringBuilder(); 13 long beginTime = System.currentTimeMillis(); 14 for(int i = 0; i < LOOP_TIMES; i++){ 15 builder.append(CONSTANT_STRING); 16 } 17 builder.toString(); 18 long endTime = System.currentTimeMillis(); 19 System.out.print("StringBuilder : " + (endTime - beginTime) + "\t"); 20 } 21 22 public void testCapacityStringBuilder(){ 23 StringBuilder builder = new StringBuilder(LOOP_TIMES * CONSTANT_STRING.length()); 24 long beginTime = System.currentTimeMillis(); 25 for(int i = 0; i < LOOP_TIMES; i++){ 26 builder.append(CONSTANT_STRING); 27 } 28 builder.toString(); 29 long endTime = System.currentTimeMillis(); 30 System.out.print("StringBuilder : " + (endTime - beginTime) + "\t"); 31 } 32 33 public void startup(){ 34 for(int i = 0; i < 10; i++){ 35 System.out.print("The " + i + " [\t "); 36 testStringBuilder(); 37 testCapacityStringBuilder(); 38 System.out.println("]"); 39 } 40 } 41 }?
示例中是頻繁的去調(diào) StringBuilder 的 append() 方法往源字符串中追加內(nèi)容, 總共測(cè)試 10 次, 下面附上測(cè)試的結(jié)果的數(shù)據(jù):
| Number | StringBuilder() | StringBuilder(int) |
| 0 | 60 | 33 |
| 1 | 43 | 26 |
| 2 | 41 | 25 |
| 3 | 42 | 24 |
| 4 | 51 | 30 |
| 5 | 92 | 24 |
| 6 | 55 | 24 |
| 7 | 40 | 24 |
| 8 | 55 | 21 |
| 9 | 44 | 21 |
| ?平均 | ?52.3 | ?25.2 |
?
?
?
?
?
?
?
?
?
?
?
?
?
?
從表格數(shù)據(jù)可以看出, 合理的指定字符串緩沖區(qū)的容量可以大大的提高系統(tǒng)的性能(若按表格的數(shù)據(jù)來算, 性能約提升了 108%), 這是因?yàn)?StringBuilder 在
緩沖區(qū)容量不足的時(shí)候會(huì)自動(dòng)擴(kuò)容, 而擴(kuò)容就會(huì)涉及到數(shù)組的拷貝(StringBuilder 和 StringBuffer 底層都是使用 char 數(shù)組來實(shí)現(xiàn)的), 這個(gè)也可以在源碼
的 expandCapacity(int) 方法中看的出。這些額外的開銷都是需要花費(fèi)掉一定量的時(shí)間的。
?
在上示代碼中, 如果將 StringBuilder 換成 StringBuffer, 其余保持不變, 測(cè)試的結(jié)果的數(shù)據(jù)如下:
| Number | SstingBuffer() | StringBuffer(int) |
| 0 | 85 | 58 |
| 1 | 70 | 56 |
| 2 | 73 | 56 |
| 3 | 71 | 55 |
| 4 | 73 | 58 |
| 5 | 117 | 55 |
| 6 | 84 | 55 |
| 7 | 69 | 55 |
| 8 | 70 | 52 |
| 9 | 73 | 52 |
| 平均? | ?78.5 | 55.2? |
?
?
?
?
?
?
?
?
?
?
?
?
?
?
與 StringBuilder 相類似的, 指定容量的構(gòu)造器在性能上也得到了較大的提升(若按表格數(shù)據(jù)來算, 性能約提升了 42%), 但由于 StringBuffer 需要
執(zhí)行同步, 因此性能上會(huì)比 StringBuilder 差一些。
from:?http://www.cnblogs.com/fancydeepin/archive/2013/04/23/min-snail-speak_String-StringBuffer-StringBuilder.html
總結(jié)
以上是生活随笔為你收集整理的小瓜牛漫谈 — String、StringBuffer、StringBuilder的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅谈 Java 字符串(String,
- 下一篇: Java 性能优化之 String 篇