<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## [`+`的重載與`StringBuilder`](https://lingcoder.gitee.io/onjava8/#/book/18-Strings?id=-%e7%9a%84%e9%87%8d%e8%bd%bd%e4%b8%8e-stringbuilder) `String`對象是不可變的,你可以給一個`String`對象添加任意多的別名。因為`String`是只讀的,所以指向它的任何引用都不可能修改它的值,因此,也就不會影響到其他引用。 不可變性會帶來一定的效率問題。為`String`對象重載的`+`操作符就是一個例子。重載的意思是,一個操作符在用于特定的類時,被賦予了特殊的意義(用于`String`的`+`與`+=`是 Java 中僅有的兩個重載過的操作符,Java 不允許程序員重載任何其他的操作符 \[^1\])。 操作符`+`可以用來連接`String`: ~~~ // strings/Concatenation.java public class Concatenation { public static void main(String[] args) { String mango = "mango"; String s = "abc" + mango + "def" + 47; System.out.println(s); } } /* Output: abcmangodef47 */ ~~~ 可以想象一下,這段代碼是這樣工作的:`String`可能有一個`append()`方法,它會生成一個新的`String`對象,以包含“abc”與`mango`連接后的字符串。該對象會再創建另一個新的`String`對象,然后與“def”相連,生成另一個新的對象,依此類推。 這種方式當然是可行的,但是為了生成最終的`String`對象,會產生一大堆需要垃圾回收的中間對象。我猜想,Java 設計者一開始就是這么做的(這也是軟件設計中的一個教訓:除非你用代碼將系統實現,并讓它運行起來,否則你無法真正了解它會有什么問題),然后他們發現其性能相當糟糕。 想看看以上代碼到底是如何工作的嗎?可以用 JDK 自帶的`javap`工具來反編譯以上代碼。命令如下: ~~~ javap -c Concatenation ~~~ 這里的`-c`標志表示將生成 JVM 字節碼。我們剔除不感興趣的部分,然后做細微的修改,于是有了以下的字節碼: ~~~ public static void main(java.lang.String[]); Code: Stack=2, Locals=3, Args_size=1 0: ldc #2; //String mango 2: astore_1 3: new #3; //class StringBuilder 6: dup 7: invokespecial #4; //StringBuilder."<init>":() 10: ldc #5; //String abc 12: invokevirtual #6; //StringBuilder.append:(String) 15: aload_1 16: invokevirtual #6; //StringBuilder.append:(String) 19: ldc #7; //String def 21: invokevirtual #6; //StringBuilder.append:(String) 24: bipush 47 26: invokevirtual #8; //StringBuilder.append:(I) 29: invokevirtual #9; //StringBuilder.toString:() 32: astore_2 33: getstatic #10; //Field System.out:PrintStream; 36: aload_2 37: invokevirtual #11; //PrintStream.println:(String) 40: return ~~~ 如果你有匯編語言的經驗,以上代碼應該很眼熟(其中的`dup`和`invokevirtual`語句相當于Java虛擬機上的匯編語句。即使你完全不了解匯編語言也無需擔心)。需要重點注意的是:編譯器自動引入了`java.lang.StringBuilder`類。雖然源代碼中并沒有使用`StringBuilder`類,但是編譯器卻自作主張地使用了它,就因為它更高效。 在這里,編譯器創建了一個`StringBuilder`對象,用于構建最終的`String`,并對每個字符串調用了一次`append()`方法,共計 4 次。最后調用`toString()`生成結果,并存為`s`(使用的命令為`astore_2`)。 現在,也許你會覺得可以隨意使用`String`對象,反正編譯器會自動為你做性能優化。可是在這之前,讓我們更深入地看看編譯器能為我們優化到什么程度。下面的例子采用兩種方式生成一個`String`:方法一使用了多個`String`對象;方法二在代碼中使用了`StringBuilder`。 ~~~ // strings/WhitherStringBuilder.java public class WhitherStringBuilder { public String implicit(String[] fields) { String result = ""; for(String field : fields) { result += field; } return result; } public String explicit(String[] fields) { StringBuilder result = new StringBuilder(); for(String field : fields) { result.append(field); } return result.toString(); } } ~~~ 現在運行`javap -c WhitherStringBuilder`,可以看到兩種不同方法(我已經去掉不相關的細節)對應的字節碼。首先是`implicit()`方法: ~~~ public java.lang.String implicit(java.lang.String[]); 0: ldc #2 // String 2: astore_2 3: aload_1 4: astore_3 5: aload_3 6: arraylength 7: istore 4 9: iconst_0 10: istore 5 12: iload 5 14: iload 4 16: if_icmpge 51 19: aload_3 20: iload 5 22: aaload 23: astore 6 25: new #3 // StringBuilder 28: dup 29: invokespecial #4 // StringBuilder."<init>" 32: aload_2 33: invokevirtual #5 // StringBuilder.append:(String) 36: aload 6 38: invokevirtual #5 // StringBuilder.append:(String;) 41: invokevirtual #6 // StringBuilder.toString:() 44: astore_2 45: iinc 5, 1 48: goto 12 51: aload_2 52: areturn ~~~ 注意從第 16 行到第 48 行構成了一個循環體。第 16 行:對堆棧中的操作數進行“大于或等于的整數比較運算”,循環結束時跳轉到第 51 行。第 48 行:重新回到循環體的起始位置(第 12 行)。注意:`StringBuilder`是在循環內構造的,這意味著每進行一次循環,會創建一個新的`StringBuilder`對象。 下面是`explicit()`方法對應的字節碼: ~~~ public java.lang.String explicit(java.lang.String[]); 0: new #3 // StringBuilder 3: dup 4: invokespecial #4 // StringBuilder."<init>" 7: astore_2 8: aload_1 9: astore_3 10: aload_3 11: arraylength 12: istore 4 14: iconst_0 15: istore 5 17: iload 5 19: iload 4 21: if_icmpge 43 24: aload_3 25: iload 5 27: aaload 28: astore 6 30: aload_2 31: aload 6 33: invokevirtual #5 // StringBuilder.append:(String) 36: pop 37: iinc 5, 1 40: goto 17 43: aload_2 44: invokevirtual #6 // StringBuilder.toString:() 47: areturn ~~~ 可以看到,不僅循環部分的代碼更簡短、更簡單,而且它只生成了一個`StringBuilder`對象。顯式地創建`StringBuilder`還允許你預先為其指定大小。如果你已經知道最終字符串的大概長度,那預先指定`StringBuilder`的大小可以避免頻繁地重新分配緩沖。 因此,當你為一個類編寫`toString()`方法時,如果字符串操作比較簡單,那就可以信賴編譯器,它會為你合理地構造最終的字符串結果。但是,如果你要在`toString()`方法中使用循環,且可能有性能問題,那么最好自己創建一個`StringBuilder`對象,用它來構建最終結果。請參考以下示例: ~~~ // strings/UsingStringBuilder.java import java.util.*; import java.util.stream.*; public class UsingStringBuilder { public static String string1() { Random rand = new Random(47); StringBuilder result = new StringBuilder("["); for(int i = 0; i < 25; i++) { result.append(rand.nextInt(100)); result.append(", "); } result.delete(result.length()-2, result.length()); result.append("]"); return result.toString(); } public static String string2() { String result = new Random(47) .ints(25, 0, 100) .mapToObj(Integer::toString) .collect(Collectors.joining(", ")); return "[" + result + "]"; } public static void main(String[] args) { System.out.println(string1()); System.out.println(string2()); } } /* Output: [58, 55, 93, 61, 61, 29, 68, 0, 22, 7, 88, 28, 51, 89, 9, 78, 98, 61, 20, 58, 16, 40, 11, 22, 4] [58, 55, 93, 61, 61, 29, 68, 0, 22, 7, 88, 28, 51, 89, 9, 78, 98, 61, 20, 58, 16, 40, 11, 22, 4] */ ~~~ 在方法`string1()`中,最終結果是用`append()`語句拼接起來的。如果你想走捷徑,例如:`append(a + ": " + c)`,編譯器就會掉入陷阱,從而為你另外創建一個`StringBuilder`對象處理括號內的字符串操作。如果拿不準該用哪種方式,隨時可以用`javap`來分析你的程序。 `StringBuilder`提供了豐富而全面的方法,包括`insert()`、`replace()`、`substring()`,甚至還有`reverse()`,但是最常用的還是`append()`和`toString()`。還有`delete()`,上面的例子中我們用它刪除最后一個逗號和空格,以便添加右括號。 `string2()`使用了`Stream`,這樣代碼更加簡潔美觀。可以證明,`Collectors.joining()`內部也是使用的`StringBuilder`,這種寫法不會影響性能! `StringBuilder` 是 Java SE5 引入的,在這之前用的是`StringBuffer`。后者是線程安全的(參見[并發編程](https://lingcoder.gitee.io/onjava8/#/./24-Concurrent-Programming)),因此開銷也會大些。使用`StringBuilder`進行字符串操作更快一點。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看