Java字符串通識及優化

字符串拼接

還記得初學Java時,字符串拼接要用StringBuilder#append方法,最後用toString方法得出最後的字符串結果。

但其實JDK會自動將使用“+”拼接轉換爲StringBuilder,虛擬機使用-XX:+OptimizeStringConcat開啓字符串拼接優化功能。

a+b 會被虛擬機編譯成 new StringBuilder().append(a).append(b).toString(); 

驗證過程

首先敲一段代碼

public class StringTest {
    public void add() {
        String a = "hello";
        String b = "world";
        String c = a + b;
        System.out.println(c);
    }
}

右鍵文件進入終端,執行javac命令獲取class文件

得出以下結果,後面的英文註釋都是自動加上的,極大方便閱讀,也可見StringBuilder的創建時機。

# mac @ macdeMacBook-Pro in ~/IdeaProjects/exam/src/com/xxx/test/base [6:46:12] 
$ javac StringTest.java 

# mac @ macdeMacBook-Pro in ~/IdeaProjects/exam/src/com/xxx/test/base [6:46:21] 
$ ls
StringTest.class StringTest.java

# mac @ macdeMacBook-Pro in ~/IdeaProjects/exam/src/com/xxx/test/base [6:46:23] 
$ javap -v StringTest.class
Classfile /Users/mac/IdeaProjects/exam/src/com/xxx/test/base/StringTest.class
  Last modified 2022-1-24; size 606 bytes
  MD5 checksum d585fe78e850590fdf95a13b80806a03
  Compiled from "StringTest.java"
public class com.huawei.test.base.StringTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#19        // java/lang/Object."<init>":()V
   #2 = String             #20            // hello
   #3 = String             #21            // world
   #4 = Class              #22            // java/lang/StringBuilder
   #5 = Methodref          #4.#19         // java/lang/StringBuilder."<init>":()V
   #6 = Methodref          #4.#23         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #7 = Methodref          #4.#24         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #8 = Fieldref           #25.#26        // java/lang/System.out:Ljava/io/PrintStream;
   #9 = Methodref          #27.#28        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #10 = Class              #29            // com/huawei/test/base/StringTest
  #11 = Class              #30            // java/lang/Object
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               add
  #17 = Utf8               SourceFile
  #18 = Utf8               StringTest.java
  #19 = NameAndType        #12:#13        // "<init>":()V
  #20 = Utf8               hello
  #21 = Utf8               world
  #22 = Utf8               java/lang/StringBuilder
  #23 = NameAndType        #31:#32        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #24 = NameAndType        #33:#34        // toString:()Ljava/lang/String;
  #25 = Class              #35            // java/lang/System
  #26 = NameAndType        #36:#37        // out:Ljava/io/PrintStream;
  #27 = Class              #38            // java/io/PrintStream
  #28 = NameAndType        #39:#40        // println:(Ljava/lang/String;)V
  #29 = Utf8               com/huawei/test/base/StringTest
  #30 = Utf8               java/lang/Object
  #31 = Utf8               append
  #32 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #33 = Utf8               toString
  #34 = Utf8               ()Ljava/lang/String;
  #35 = Utf8               java/lang/System
  #36 = Utf8               out
  #37 = Utf8               Ljava/io/PrintStream;
  #38 = Utf8               java/io/PrintStream
  #39 = Utf8               println
  #40 = Utf8               (Ljava/lang/String;)V
{
  public com.huawei.test.base.StringTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 8: 0

  public void add();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: ldc           #2                  // String hello
         2: astore_1
         3: ldc           #3                  // String world
         5: astore_2
         6: new           #4                  // class java/lang/StringBuilder
         9: dup
        10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        13: aload_1
        14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        17: aload_2
        18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: astore_3
        25: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        28: aload_3
        29: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        32: return
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 6
        line 13: 25
        line 14: 32
}
SourceFile: "StringTest.java"

令人驚奇的是以下代碼在JIT不會被優化。

StringBuilder sb = new StringBuilder();
sb.apend(a);
sb.append(b)

當然還有StringBuffer這種線程安全的類及方法,在大部分場景下的字符串拼接是不涉及線程同步的。so 比較少用。

字符串格式化

在項目中記錄日誌時通常會用 log.info("物流單已生成,單號:{}",orderNumber),此類的佔位符,並且後續的參數是不定長的,可以擴展多個佔位符,非常好用。

String.format

String fmt = "hello %s";
String res = String.format(fmt,"world"); // hello world

MessageFormat.format

其實在JDK包也提供了MessageFormat#format方法來實現類似的功能。部分源碼如下:

package java.text;  // 屬於java text包下
public class MessageFormat extends Format {
// ...
	//核心方法
	 public static String format(String var0, Object... var1) {
        MessageFormat var2 = new MessageFormat(var0);
        return var2.format(var1); //此處調用父類Format的format方法。format(obj, new StringBuffer(), new FieldPosition(0)).toString();
    }
}

使用方法

MessageFormat.format("myname is {0},age is {1}","Lucy","12"); // myname is Lucy,age is 12

字符串匹配

構造字典樹(也稱前綴樹)來匹配對應的文字。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章