重温java中的String,StringBuffer,StringBuilder类
不論什么一個(gè)系統(tǒng)在開(kāi)發(fā)的過(guò)程中, 相信都不會(huì)缺少對(duì)字符串的處理。
在 java 語(yǔ)言中, 用來(lái)處理字符串的的類(lèi)經(jīng)常使用的有 3 個(gè):?String、StringBuffer、StringBuilder。
它們的異同點(diǎn):
1) 都是 final 類(lèi), 都不同意被繼承;
2) String 長(zhǎng)度是不可變的, StringBuffer、StringBuilder 長(zhǎng)度是可變的;
3) StringBuffer 是線程安全的, StringBuilder 不是線程安全的。
String 類(lèi)已在上一篇隨筆?小瓜牛漫談 — String?中敘述過(guò), 這里就不再贅述。
本篇隨筆意在漫游 StringBuffer 與 StringBuilder。
事實(shí)上如今網(wǎng)絡(luò)上談?wù)?String、StringBuffer、StringBuilder 的文章已經(jīng)多到不可勝數(shù)了。小瓜牛不才, 蝸行牛步, 慢了半個(gè)世紀(jì)。
。。
StringBuilder 與 StringBuffer 支持的全部操作基本上是一致的, 不同的是, StringBuilder 不須要運(yùn)行同步。
同步操作意味著
要耗費(fèi)系統(tǒng)的一些額外的開(kāi)銷(xiāo), 或時(shí)間, 或空間, 或資源等, 甚至可能會(huì)造成死鎖。從理論上來(lái)講,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)的過(guò)程, 在本人的筆記本電腦上總共花去了 23 分鐘之多, 以下附上詳細(xì)數(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?中提到過(guò), 底層實(shí)際上是將循環(huán)體內(nèi)的 string += CONSTANT_STRING; 語(yǔ)句轉(zhuǎn)成了:
string = (new StringBuilder(String.valueOf(string))).append("min-snail").toString();
所以在二十萬(wàn)次的串聯(lián)字符串中, 每一次都先去創(chuàng)建 StringBuilder 對(duì)象, 然后再調(diào) append() 方法來(lái)完畢 String 類(lèi)的 "+" 操作。
這里的大部分時(shí)間都花在了對(duì)象的創(chuàng)建上, 并且每一個(gè)創(chuàng)建出來(lái)的對(duì)象的生命都不能長(zhǎng)久, 朝生夕滅, 由于這些對(duì)象創(chuàng)建出來(lái)之后沒(méi)有引用變量來(lái)引用它們,
那么它們?cè)谑褂猛戤厱r(shí)候就處于一種不可到達(dá)狀態(tài), java 虛擬機(jī)的垃圾回收器(GC)就會(huì)不定期的來(lái)回收這些垃圾對(duì)象。
因此會(huì)看到上圖堆內(nèi)存中的曲線起伏變化非常大。
?
但假設(shè)是遇到例如以下情況:
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 毫秒。這是由于 concat1 在編譯期就能夠被確定是一個(gè)字符常量。
當(dāng)編譯完畢之后 concat1 的值事實(shí)上就是?"I am min-snail", 因此, 在執(zhí)行期間自然就不須要花費(fèi)太多的時(shí)間來(lái)處理 concat1 了。假設(shè)是站在這個(gè)角度來(lái)看, 使用
StringBuilder 全然不占優(yōu)勢(shì), 在這樣的情況下, 假設(shè)是使用 StringBuilder 反而會(huì)使得程序執(zhí)行須要耗費(fèi)很多其它的時(shí)間。
可是 concat2 不一樣, 因?yàn)?concat2 在編譯期間不可以被確定, 因此, 在執(zhí)行期間 JVM 會(huì)按老一套的做法, 將其轉(zhuǎn)換成使用 StringBuilder 來(lái)實(shí)現(xiàn)。
2) 從表格數(shù)據(jù)能夠看出, StringBuilder 與 StringBuffer 在耗時(shí)上并不相差多少, 僅僅是 StringBuilder 略微快一些, 可是 StringBuilder 是
冒著多線程不安全的潛在風(fēng)險(xiǎn)。
這也是 StringBuilder 為賺取表格數(shù)據(jù)中的 1.7 毫秒( 若按表格的數(shù)據(jù)來(lái)算, 性能已經(jīng)提升 20% 多 )所須要付出的代價(jià)。
3) 綜合來(lái)說(shuō):
StringBuilder 是 java 為 StringBuffer 提供的一個(gè)等價(jià)類(lèi), 但不保證同步。
在不涉及多線程的操作情況下能夠簡(jiǎn)易的替換 StringBuffer 來(lái)提升
系統(tǒng)性能; StringBuffer 在性能上稍略于 StringBuilder, 但能夠不用考慮線程安全問(wèn)題; String 的 "+"符號(hào)操作起來(lái)簡(jiǎn)單方便,
String 的使用也非常easy便捷, java 底層會(huì)轉(zhuǎn)換成 StringBuilder 來(lái)實(shí)現(xiàn), 特別假設(shè)是要在循環(huán)體內(nèi)使用, 建議選擇其余兩個(gè)。?
?
使用 StringBuffer、StringBuilder 的無(wú)參構(gòu)造器產(chǎn)生的對(duì)象默認(rèn)擁有 16 個(gè)字符長(zhǎng)度大小的字符串緩沖區(qū), 假設(shè)是調(diào)參數(shù)為 String 的構(gòu)造器,
默認(rèn)的字符串緩沖區(qū)容量是 String 對(duì)象的長(zhǎng)度 + 16 個(gè)長(zhǎng)度的大小(留 16 個(gè)長(zhǎng)度大小的空緩沖區(qū))。具體信息可見(jiàn) StringBuilder 源代碼:
當(dāng)使用 append 或 insert 方法向源字符串追加內(nèi)容的時(shí)候, 假設(shè)內(nèi)部緩沖區(qū)的大小不夠, 就會(huì)自己主動(dòng)擴(kuò)張容量, 詳細(xì)信息看 AbstractStringBuilder 源代碼:
StringBuffer 與 StringBuilder 是相類(lèi)似的, 這里就不貼 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() 方法是用來(lái)計(jì)算對(duì)象字符串緩沖區(qū)的總?cè)萘看笮?
builder1 為: length + 16 = 0 + 16 = 16;
builder3 為: length + 16 = 9 + 16 = 25;
builder2 因?yàn)槭侵苯又付ㄗ址彌_區(qū)的大小, 因此容量就是指定的值 10, 這個(gè)從源代碼的構(gòu)造器中就能非常easy的看出;
當(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ù)來(lái)算, 性能約提升了 108%), 這是由于 StringBuilder 在
緩沖區(qū)容量不足的時(shí)候會(huì)自己主動(dòng)擴(kuò)容, 而擴(kuò)容就會(huì)涉及到數(shù)組的拷貝(StringBuilder 和 StringBuffer 底層都是使用 char 數(shù)組來(lái)實(shí)現(xiàn)的), 這個(gè)也能夠在源代碼
的 expandCapacity(int) 方法中看的出。
這些額外的開(kāi)銷(xiāo)都是須要花費(fèi)掉一定量的時(shí)間的。
在上示代碼中, 假設(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 相類(lèi)似的, 指定容量的構(gòu)造器在性能上也得到了較大的提升(若按表格數(shù)據(jù)來(lái)算, 性能約提升了 42%), 但因?yàn)?StringBuffer 須要
運(yùn)行同步, 因此性能上會(huì)比 StringBuilder 差一些。
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專(zhuān)家共同創(chuàng)作,文字、視頻、音頻交互閱讀
總結(jié)
以上是生活随笔為你收集整理的重温java中的String,StringBuffer,StringBuilder类的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 200多位阿里工程师齐聚“光明顶”,双1
- 下一篇: 装饰器前戏