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

字符串匹配

构造字典树(也称前缀树)来匹配对应的文字。

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