自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

教妹學(xué) Java:字符串拼接

開發(fā) 后端
“+ 號操作符其實(shí)被 Java 在編譯的時(shí)候重新解釋了,換一種說法就是,+ 號操作符是一種語法糖,讓字符串的拼接變得更簡便了?!币贿吔o三妹解釋,我一邊在 Intellij IDEA 中敲出了下面這段代碼。

[[405355]]

“哥,你讓我看的《Java 開發(fā)手冊》上有這么一段內(nèi)容:循環(huán)體內(nèi),拼接字符串最好使用 StringBuilder 的 append() 方法,而不是 + 號操作符。這是為什么呀?”三妹疑惑地問。

“好的,三妹,哥來慢慢給你講。”我回答。

三妹能在學(xué)習(xí)的過程中不斷地發(fā)現(xiàn)問題,讓我感到非常的開心。其實(shí)很多時(shí)候,我們不應(yīng)該只是把知識點(diǎn)記在心里,還應(yīng)該問一問自己,到底是為什么,只有邁出去這一步,才能真正的成長起來。

“+ 號操作符其實(shí)被 Java 在編譯的時(shí)候重新解釋了,換一種說法就是,+ 號操作符是一種語法糖,讓字符串的拼接變得更簡便了。”一邊給三妹解釋,我一邊在 Intellij IDEA 中敲出了下面這段代碼。

  1. class Demo { 
  2.     public static void main(String[] args) { 
  3.         String chenmo = "沉默"
  4.         String wanger = "王二"
  5.         System.out.println(chenmo + wanger); 
  6.     } 

在 Java 8 的環(huán)境下,使用 javap -c Demo.class 反編譯字節(jié)碼后,可以看到以下內(nèi)容:

  1. Compiled from "Demo.java" 
  2. class Demo { 
  3.   Demo(); 
  4.     Code: 
  5.        0: aload_0 
  6.        1: invokespecial #1                  // Method java/lang/Object."<init>":()V 
  7.        4: return 
  8.  
  9.   public static void main(java.lang.String[]); 
  10.     Code: 
  11.        0: ldc           #2                  // String 沉默 
  12.        2: astore_1 
  13.        3: ldc           #3                  // String 王二 
  14.        5: astore_2 
  15.        6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream; 
  16.        9: new           #5                  // class java/lang/StringBuilder 
  17.       12: dup 
  18.       13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V 
  19.       16: aload_1 
  20.       17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
  21.       20: aload_2 
  22.       21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
  23.       24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
  24.       27: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
  25.       30: return 

“你看,三妹,這里有一個(gè) new 關(guān)鍵字,并且 class 類型為 java/lang/StringBuilder。”我指著標(biāo)號為 9 的那行對三妹說,“這意味著新建了一個(gè) StringBuilder 的對象。”

“然后看標(biāo)號為 17 的這行,是一個(gè) invokevirtual 指令,用于調(diào)用對象的方法,也就是 StringBuilder 對象的 append() 方法。”

“也就意味著把 chenmo 這個(gè)字符串添加到 StringBuilder 對象中了。”

“再往下看,標(biāo)號為 21 的這行,又調(diào)用了一次 append() 方法,意味著把 wanger 這個(gè)字符串添加到 StringBuilder 對象中了。”

換成 Java 代碼來表示的話,大概是這個(gè)樣子:

  1. class Demo { 
  2.     public static void main(String[] args) { 
  3.         String chenmo = "沉默"
  4.         String wanger = "王二"
  5.         System.out.println((new StringBuilder(String.valueOf(chenmo))).append(wanger).toString()); 
  6.     } 

“哦,原來編譯的時(shí)候把“+”號操作符替換成了 StringBuilder 的 append() 方法啊。”三妹恍然大悟。

“是的,不過到了 Java 9,情況發(fā)生了一些改變,同樣的代碼,字節(jié)碼指令完全不同了。”我說。

同樣的代碼,在 Java 11 的環(huán)境下,字節(jié)碼指令是這樣的:

  1. Compiled from "Demo.java" 
  2. public class com.itwanger.thirtyseven.Demo { 
  3.   public com.itwanger.thirtyseven.Demo(); 
  4.     Code: 
  5.        0: aload_0 
  6.        1: invokespecial #1                  // Method java/lang/Object."<init>":()V 
  7.        4: return 
  8.  
  9.   public static void main(java.lang.String[]); 
  10.     Code: 
  11.        0: ldc           #2                  // String 
  12.        2: astore_1 
  13.        3: iconst_0 
  14.        4: istore_2 
  15.        5: iload_2 
  16.        6: bipush        10 
  17.        8: if_icmpge     41 
  18.       11: new           #3                  // class java/lang/String 
  19.       14: dup 
  20.       15: ldc           #4                  // String 沉默 
  21.       17: invokespecial #5                  // Method java/lang/String."<init>":(Ljava/lang/String;)V 
  22.       20: astore_3 
  23.       21: ldc           #6                  // String 王二 
  24.       23: astore        4 
  25.       25: aload_1 
  26.       26: aload_3 
  27.       27: aload         4 
  28.       29: invokedynamic #7,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 
  29.       34: astore_1 
  30.       35: iinc          2, 1 
  31.       38: goto          5 
  32.       41: return 

看標(biāo)號為 29 的這行,字節(jié)碼指令為 invokedynamic,該指令允許由應(yīng)用級的代碼來決定方法解析,所謂的應(yīng)用級的代碼其實(shí)是一個(gè)方法——被稱為引導(dǎo)方法(Bootstrap Method),簡稱 BSM,BSM 會返回一個(gè) CallSite(調(diào)用點(diǎn)) 對象,這個(gè)對象就和 invokedynamic 指令鏈接在一起。以后再執(zhí)行這條 invokedynamic 指令時(shí)就不會創(chuàng)建新的 CallSite 對象。CallSite 其實(shí)就是一個(gè) MethodHandle(方法句柄)的 holder,指向一個(gè)調(diào)用點(diǎn)真正執(zhí)行的方法——此時(shí)就是 StringConcatFactory.makeConcatWithConstants() 方法。

“哥,你別再說了,再說我就聽不懂了。”三妹打斷了我的話。

“好吧,總之就是 Java 9 以后,JDK 用了另外一種方法來動態(tài)解釋 + 號操作符,具體的實(shí)現(xiàn)方式在字節(jié)碼指令層面已經(jīng)看不到了,所以我就以 Java 8 來繼續(xù)講解吧。”

“再回到《Java 開發(fā)手冊》上的那段內(nèi)容:循環(huán)體內(nèi),拼接字符串最好使用 StringBuilder 的 append() 方法,而不是 + 號操作符。原因就在于循環(huán)體內(nèi)如果用 + 號操作符的話,就會產(chǎn)生大量的 StringBuilder 對象,不僅占用了更多的內(nèi)存空間,還會讓 Java 虛擬機(jī)不同的進(jìn)行垃圾回收,從而降低了程序的性能。”

更好的寫法就是在循環(huán)的外部新建一個(gè) StringBuilder 對象,然后使用 append() 方法將循環(huán)體內(nèi)的字符串添加進(jìn)來:

  1. class Demo { 
  2.     public static void main(String[] args) { 
  3.         StringBuilder sb = new StringBuilder(); 
  4.         for (int i = 1; i < 10; i++) { 
  5.             String chenmo = "沉默"
  6.             String wanger = "王二"
  7.             sb.append(chenmo); 
  8.             sb.append(wanger); 
  9.         } 
  10.         System.out.println(sb); 
  11.     } 

來做個(gè)小測試。

第一個(gè),for 循環(huán)中使用”+”號操作符。

  1. String result = ""
  2. for (int i = 0; i < 100000; i++) { 
  3.     result += "六六六"

第二個(gè),for 循環(huán)外部新建 StringBuilder,循環(huán)體內(nèi)使用 append() 方法。

  1. StringBuilder sb = new StringBuilder(); 
  2. for (int i = 0; i < 100000; i++) { 
  3.     sb.append("六六六"); 

“這兩個(gè)小測試分別會耗時(shí)多長時(shí)間呢?三妹你來運(yùn)行下。”

“哇,第一個(gè)小測試的執(zhí)行時(shí)間是 6212 毫秒,第二個(gè)只用了不到 1 毫秒,差距也太大了吧!”三妹說。

“是的,這下明白了原因吧?”我說。

“是的,哥,原來如此。”

“好了,三妹,來看一下 StringBuilder 類的 append() 方法的源碼吧!”

  1. public StringBuilder append(String str) { 
  2.     super.append(str); 
  3.     return this; 

這 3 行代碼其實(shí)沒啥看的。我們來看父類 AbstractStringBuilder 的 append() 方法:

  1. public AbstractStringBuilder append(String str) { 
  2.     if (str == null
  3.         return appendNull(); 
  4.     int len = str.length(); 
  5.     ensureCapacityInternal(count + len); 
  6.     str.getChars(0, len, value, count); 
  7.     count += len; 
  8.     return this; 

1)判斷拼接的字符串是不是 null,如果是,當(dāng)做字符串“null”來處理。appendNull() 方法的源碼如下:

  1. private AbstractStringBuilder appendNull() { 
  2.     int c = count
  3.     ensureCapacityInternal(c + 4); 
  4.     final char[] value = this.value; 
  5.     value[c++] = 'n'
  6.     value[c++] = 'u'
  7.     value[c++] = 'l'
  8.     value[c++] = 'l'
  9.     count = c; 
  10.     return this; 

2)獲取字符串的長度。

3)ensureCapacityInternal() 方法的源碼如下:

  1. private void ensureCapacityInternal(int minimumCapacity) { 
  2.     // overflow-conscious code 
  3.     if (minimumCapacity - value.length > 0) { 
  4.         value = Arrays.copyOf(value, 
  5.                 newCapacity(minimumCapacity)); 
  6.     } 

由于字符串內(nèi)部是用數(shù)組實(shí)現(xiàn)的,所以需要先判斷拼接后的字符數(shù)組長度是否超過當(dāng)前數(shù)組的長度,如果超過,先對數(shù)組進(jìn)行擴(kuò)容,然后把原有的值復(fù)制到新的數(shù)組中。

4)將拼接的字符串 str 復(fù)制到目標(biāo)數(shù)組 value 中。

  1. str.getChars(0, len, value, count

5)更新數(shù)組的長度 count。

“說到 StringBuilder 就必須得提一嘴 StringBuffer,兩者就像是孿生雙胞胎,該有的都有,只不過大哥 StringBuffer 因?yàn)槎嗪粑鼉煽谛迈r空氣,所以是線程安全的。”我說,“它里面的方法基本上都加了 synchronized 關(guān)鍵字來做同步。”

  1. public synchronized StringBuffer append(String str) { 
  2.     toStringCache = null
  3.     super.append(str); 
  4.     return this; 

“除了可以使用 + 號操作符,StringBuilder 和 StringBuilder 的 append() 方法,還有其他的字符串拼接方法嗎?”三妹問。

“有啊,比如說 String 類的 concat() 方法,有點(diǎn)像 StringBuilder 類的 append() 方法。”

  1. String chenmo = "沉默"
  2. String wanger = "王二"
  3. System.out.println(chenmo.concat(wanger)); 

可以來看一下 concat() 方法的源碼。

  1. public String concat(String str) { 
  2.     int otherLen = str.length(); 
  3.     if (otherLen == 0) { 
  4.         return this; 
  5.     } 
  6.     int len = value.length; 
  7.     char buf[] = Arrays.copyOf(value, len + otherLen); 
  8.     str.getChars(buf, len); 
  9.     return new String(buf, true); 

1)如果拼接的字符串的長度為 0,那么返回拼接前的字符串。

2)將原字符串的字符數(shù)組 value 復(fù)制到變量 buf 數(shù)組中。

3)把拼接的字符串 str 復(fù)制到字符數(shù)組 buf 中,并返回新的字符串對象。

我一行一行地給三妹解釋著。

“和 + 號操作符相比,concat() 方法在遇到字符串為 null 的時(shí)候,會拋出 NullPointerException,而“+”號操作符會把 null 當(dāng)做是“null”字符串來處理。”

如果拼接的字符串是一個(gè)空字符串(""),那么 concat 的效率要更高一點(diǎn),畢竟不需要 new StringBuilder 對象。

如果拼接的字符串非常多,concat() 的效率就會下降,因?yàn)閯?chuàng)建的字符串對象越來越多。

“還有嗎?”三妹似乎對字符串拼接很感興趣。

“有,當(dāng)然有。”

String 類有一個(gè)靜態(tài)方法 join(),可以這樣來使用。

  1. String chenmo = "沉默"
  2. String wanger = "王二"
  3. String cmower = String.join("", chenmo, wanger); 
  4. System.out.println(cmower); 

第一個(gè)參數(shù)為字符串連接符,比如說:

  1. String message = String.join("-""王二""太特么""有趣了"); 

輸出結(jié)果為:王二-太特么-有趣了。

來看一下 join 方法的源碼:

  1. public static String join(CharSequence delimiter, CharSequence... elements) { 
  2.     Objects.requireNonNull(delimiter); 
  3.     Objects.requireNonNull(elements); 
  4.     // Number of elements not likely worth Arrays.stream overhead. 
  5.     StringJoiner joiner = new StringJoiner(delimiter); 
  6.     for (CharSequence cs: elements) { 
  7.         joiner.add(cs); 
  8.     } 
  9.     return joiner.toString(); 

里面新建了一個(gè)叫 StringJoiner 的對象,然后通過 for-each 循環(huán)把可變參數(shù)添加了進(jìn)來,最后調(diào)用 toString() 方法返回 String。

“實(shí)際的工作中,org.apache.commons.lang3.StringUtils 的 join() 方法也經(jīng)常用來進(jìn)行字符串拼接。”

  1. String chenmo = "沉默"
  2. String wanger = "王二"
  3. StringUtils.join(chenmo, wanger); 

該方法不用擔(dān)心 NullPointerException。

  1. StringUtils.join(null)            = null 
  2. StringUtils.join([])              = "" 
  3. StringUtils.join([null])          = "" 
  4. StringUtils.join(["a""b""c"]) = "abc" 
  5. StringUtils.join([null"""a"]) = "a" 

來看一下源碼:

  1. public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) { 
  2.     if (array == null) { 
  3.         return null
  4.     } 
  5.     if (separator == null) { 
  6.         separator = EMPTY; 
  7.     } 
  8.  
  9.     final StringBuilder buf = new StringBuilder(noOfItems * 16); 
  10.  
  11.     for (int i = startIndex; i < endIndex; i++) { 
  12.         if (i > startIndex) { 
  13.             buf.append(separator); 
  14.         } 
  15.         if (array[i] != null) { 
  16.             buf.append(array[i]); 
  17.         } 
  18.     } 
  19.     return buf.toString(); 

內(nèi)部使用的仍然是 StringBuilder。

“好了,三妹,關(guān)于字符串拼接的知識點(diǎn)我們就講到這吧。注意 Java 9 以后,對 + 號操作符的解釋和之前發(fā)生了變化,字節(jié)碼指令已經(jīng)不同了,等后面你學(xué)了字節(jié)碼指令后我們再詳細(xì)地講一次。”我說。

“嗯,哥,你休息吧,我把這些例子再重新跑一遍。”三妹說。

本文轉(zhuǎn)載自微信公眾號「沉默王二」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系沉默王二公眾號。

 

責(zé)任編輯:武曉燕 來源: 沉默王二
相關(guān)推薦

2021-06-06 20:56:48

Java內(nèi)存 intern

2021-05-10 11:38:07

Java數(shù)組IDEA

2020-10-26 09:36:45

Java變量數(shù)據(jù)

2020-11-18 09:44:49

Java命名約定

2021-07-08 22:43:41

ThrowThrowsJava

2021-07-03 17:53:52

Java異常處理機(jī)制

2021-07-26 17:22:02

Java

2020-10-29 10:28:31

Java數(shù)據(jù)類型

2013-06-24 15:16:29

Java字符串拼接

2023-10-31 18:57:02

Java字符串

2021-05-31 07:57:00

拼接字符串Java

2021-07-30 09:32:55

JavaEquals

2019-02-27 09:08:20

Java 8StringJoineIDEA

2016-10-12 10:18:53

Java字符串源碼分析

2019-12-25 15:41:50

JavaScript程序員編程語言

2021-10-31 23:01:50

語言拼接字符串

2011-07-11 16:00:22

字符串拼接

2022-11-25 07:53:26

bash腳本字符串

2020-11-13 10:29:37

流程控制語句

2011-07-11 15:36:44

JavaScript
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號