Java字符串模板格式化彙總8法(附性能對比)

Java字符串模板格式化彙總8法(附性能對比)

 

結論: 

1. 循環中, 不要用+進行字符串拼接, 而用StringBuilder.append()方法

2. 非循環中, 字符串拼接使用+性能最高, 其次是StringBuilder.append()方法

 

 

 

 

 

1. ++

對於初學JAVA的蒙童,大約都會使用這招

@Test
public void testAdd(){

    Date now = new Date();

    String yearMonth = DateUtil.toString(now, DatePattern.YEAR_AND_MONTH);
    String expressDeliveryType = "sf";
    String fileName = DateUtil.toString(now, DatePattern.TIMESTAMP);

    String template = "/home/expressdelivery/" + yearMonth + "/" + expressDeliveryType + "/vipQuery_" + fileName + ".log";

    System.out.println(template);
}

輸出 :

/home/expressdelivery/2017-07/sf/vipQuery_20170723042314.log

2. StringBuffer / StringBuilder

@Test
public void testStringBuilder(){

    Date now = new Date();

    String yearMonth = DateUtil.toString(now, DatePattern.YEAR_AND_MONTH);
    String expressDeliveryType = "sf";
    String fileName = DateUtil.toString(now, DatePattern.TIMESTAMP);

    StringBuilder sb = new StringBuilder();
    sb.append("/home/expressdelivery/");
    sb.append(yearMonth);
    sb.append("/");
    sb.append(expressDeliveryType);
    sb.append("/vipQuery_");
    sb.append(fileName);
    sb.append(".log");

    String template = sb.toString();

    System.out.println(template);
}

輸出 :

/home/expressdelivery/2017-07/sf/vipQuery_20170723042603.log

缺點:

  • 代碼太長了

3. StringUtil.format(String, Object…​)

使用 com.feilong.core.lang.StringUtil.format(String, Object…​)

內部封裝了 String.format(String, Object)

@Test
public void testStringFormat(){
    Date now = new Date();

    String yearMonth = DateUtil.toString(now, DatePattern.YEAR_AND_MONTH);
    String expressDeliveryType = "sf";
    String fileName = DateUtil.toString(now, DatePattern.TIMESTAMP);

    String template = StringUtil.format("/home/expressdelivery/%s/%s/vipQuery_%s.log", yearMonth, expressDeliveryType, fileName);

    System.out.println(template);
}

輸出 :

/home/expressdelivery/2017-07/sf/vipQuery_20170723043153.log

4. MessageFormatUtil.format(String, Object…​)

使用 com.feilong.core.text.MessageFormatUtil.format(String, Object…​)

內部封裝了 java.text.MessageFormat.format(String, Object…​)

@Test
public void testMessageFormat(){
    Date now = new Date();

    String yearMonth = DateUtil.toString(now, DatePattern.YEAR_AND_MONTH);
    String expressDeliveryType = "sf";
    String fileName = DateUtil.toString(now, DatePattern.TIMESTAMP);

    String template = MessageFormatUtil
                    .format("/home/expressdelivery/{0}/{1}/vipQuery_{2}.log", yearMonth, expressDeliveryType, fileName);

    System.out.println(template);
}

輸出 :

/home/expressdelivery/2017-07/sf/vipQuery_20170723043153.log

5. Slf4jUtil.format(String, Object…​)

使用 com.feilong.tools.slf4j.Slf4jUtil.format(String, Object…​)

藉助 slf4j 日誌佔位符

@Test
public void testSlf4jFormat(){
    Date now = new Date();

    String yearMonth = DateUtil.toString(now, DatePattern.YEAR_AND_MONTH);
    String expressDeliveryType = "sf";
    String fileName = DateUtil.toString(now, DatePattern.TIMESTAMP);

    String template = Slf4jUtil.format("/home/expressdelivery/{}/{}/vipQuery_{}.log", yearMonth, expressDeliveryType, fileName);

    System.out.println(template);
}

輸出:

/home/expressdelivery/2017-07/sf/vipQuery_20170723144236.log

6. StringUtil.replace(CharSequence, Map<String, V>)

使用 com.feilong.core.lang.StringUtil.replace(CharSequence, Map<String, V>)

內部封裝了 apache commons-lang3 org.apache.commons.lang3.text.StrSubstitutor , 現在叫 commons-text

使用給定的字符串 templateString 作爲模板,解析匹配的變量 .

@Test
public void testReplace(){
    Date date = new Date();
    Map<String, String> map = new HashMap<>();
    map.put("yearMonth", DateUtil.toString(date, YEAR_AND_MONTH));
    map.put("expressDeliveryType", "sf");
    map.put("fileName", DateUtil.toString(date, TIMESTAMP));

    String template = StringUtil.replace("/home/expressdelivery/${yearMonth}/${expressDeliveryType}/vipQuery_${fileName}.log", map);

    System.out.println(template);
}

輸出:

/home/expressdelivery/2017-07/sf/vipQuery_20170723144608.log

優點:

  • 模塊可以定義變量名字了,不怕混亂

Note

此方法只能替換字符串,而不能像el表達式一樣使用對象屬性之類的來替換

7. VelocityUtil.parseString(String, Map<String, ?>)

使用 com.feilong.tools.velocity.VelocityUtil.parseString(String, Map<String, ?>)

該方法需要 jar

<dependency>
  <groupId>com.feilong.platform.tools</groupId>
  <artifactId>feilong-tools-velocity</artifactId>
  <version>${version.feilong-platform}</version>
</dependency>
@Test
public void testVelocityParseString(){
    Date date = new Date();

    Map<String, String> map = new HashMap<>();
    map.put("yearMonth", DateUtil.toString(date, YEAR_AND_MONTH));
    map.put("expressDeliveryType", "sf");
    map.put("fileName", DateUtil.toString(date, TIMESTAMP));

    VelocityUtil velocityUtil = new VelocityUtil();

    String template = velocityUtil
                    .parseString("/home/expressdelivery/${yearMonth}/${expressDeliveryType}/vipQuery_${fileName}.log", map);

    System.out.println(template);
}

輸出

/home/expressdelivery/2017-07/sf/vipQuery_20170723145856.log

8. VelocityUtil.parseTemplateWithClasspathResourceLoader(String, Map<String, ?>)

使用 com.feilong.tools.velocity.VelocityUtil.parseTemplateWithClasspathResourceLoader(String, Map<String, ?>)

該方法需要 jar

<dependency>
  <groupId>com.feilong.platform.tools</groupId>
  <artifactId>feilong-tools-velocity</artifactId>
  <version>${version.feilong-platform}</version>
</dependency>

該方法適合於 字符串模板獨立成文件, 方便維護

路徑是classpath 下面, 比如 velocity/path.vm

28497395 a52f617c 6fb8 11e7 9902 66249985242a

此時代碼需要如此調用:

@Test
public void testVelocityParseTemplateWithClasspathResourceLoader(){
    Date date = new Date();

    Map<String, String> map = new HashMap<>();
    map.put("yearMonth", DateUtil.toString(date, YEAR_AND_MONTH));
    map.put("expressDeliveryType", "sf");
    map.put("fileName", DateUtil.toString(date, TIMESTAMP));

    VelocityUtil velocityUtil = new VelocityUtil();

    String template = velocityUtil.parseTemplateWithClasspathResourceLoader("velocity/path.vm", map);

    System.out.println(template);
}

輸出 :

/home/expressdelivery/2017-07/sf/vipQuery_20170723150443.log

9. 性能對比

28501853 87f4c8c2 7017 11e7 99db 3b2d47c803a8

上圖是 分別循環 100000, 500000, 1000000, 2000000 統計出來的數據

在性能上 String format 是最差的

字符串 ++ 最快, StringBuilder 次之 , slf4j 的格式化再次之

測試硬件概覽:

型號名稱:	MacBook Pro
處理器名稱:	Intel Core i5
處理器速度:	2.9 GHz
內存:	16 GB

10. 參考

 

 

轉載:

Java字符串模板格式化彙總8法(附性能對比) - 飛天奔月的個人空間 - OSCHINA
https://my.oschina.net/venusdrogon/blog/1486633

 

 

 

 

 

 

 

========================================================================

========================================================================

========================================================================

 

 

字符串連接你用+還是用StringBuilder - 掘金
https://juejin.im/post/5b2af17de51d4558c713507c#heading-2

 

 

 

 

 

 

字符串連接你用+還是用StringBuilder

前言

據我所知字符串確實已經成爲 Java 開發人員最常用的類了,而且是大量使用。我們都知道,String 其實是封裝了字符,所以倆字符串連接就是將字符串對象裏面的字符連起來。很多人習慣使用+來連接字符串,也有人會用 StringBuilder 的append方法。

"+"編譯後

看看如果我們在程序中直接使用+來連接字符串的情況,用下面一個簡單的例子來說明,進行兩個字符串連接操作,即s3 = s1 + s2

public class TestString {
	public static void main(String[] args) {
		String s1 = "www";
		String s2 = "ccc";
		String s3 = s1 + s2;
	}
}
複製代碼

接着javap -c TestString.class看一下編譯後的情況,可以看到編譯器其實是對+進行了轉換的,轉成了 StringBuilder 對象來操作了,首先使用 s1 創建 StringBuilder 對象,然後用 append方法連接 s2,最後調用toString方法完成。

public class com.seaboat.string.TestString {
  public com.seaboat.string.TestString();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #16                 // String www
       2: astore_1
       3: ldc           #18                 // String ccc
       5: astore_2
       6: new           #20                 // class java/lang/StringBuilder
       9: dup
      10: aload_1
      11: invokestatic  #22                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
      14: invokespecial #28                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
      17: aload_2
      18: invokevirtual #31                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: invokevirtual #35                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      24: astore_3
      25: return
}
複製代碼

"+"與"append"等價嗎

前面可以看到+在編譯器作用下都會轉成 StringBuilder 的append方法執行,所以如果拋開運行效率來說,它們其實本質是一樣的。

本質一樣是否就能說明它們時等價的呢?或者說能否爲了方便直接用+來連接字符串,剩下的事就交給編譯器了?繼續看個例子,在這個例子中有個 for 循環進行字符串連接操作。

public class TestString2 {
	public static void main(String[] args) {
		String s = "www";
		for (int i = 0; i < 10; i++)
			s += i;
	}
}
複製代碼

編譯後的情況如下,不熟悉指令沒關係,我們只看重要的部分,if_icmplt 8,這個就是 for 循環的條件判斷,小於10則不斷跳到8的位置,8後面其實就是創建 StringBuilder 對象,並以本地變量s的值初始化該對象,接着再將本地變量i append到 StringBuilder 對象中,最後調用toString方法將所得值存到本地變量s。

這樣來看循環中每次都要創建 StringBuilder 對象,而且要調用toString方法,這樣的執行效率顯然比較低,而且增加了 GC 的壓力。

public class com.seaboat.string.TestString2 {
  public com.seaboat.string.TestString2();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #16                 // String www
       2: astore_1
       3: iconst_0
       4: istore_2
       5: goto          30
       8: new           #18                 // class java/lang/StringBuilder
      11: dup
      12: aload_1
      13: invokestatic  #20                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
      16: invokespecial #26                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
      19: iload_2
      20: invokevirtual #29                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      23: invokevirtual #33                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      26: astore_1
      27: iinc          2, 1
      30: iload_2
      31: bipush        10
      33: if_icmplt     8
      36: return
}
複製代碼

友好寫法

把事情都丟給編譯器是不友好的,爲了能讓程序執行更加高效,最好是我們自己來控制 StringBuilder 的實例,比如下面,只創建一個 StringBuilder 實例,後面用append方法連接字符串。

public class TestString3 {
	public static void main(String[] args) {
		StringBuilder sb = new StringBuilder("www");
		for (int i = 0; i < 10; i++)
			sb.append(i);
	}
}
複製代碼

編譯後的情況如下,首先創建一個 StringBuilder 對象,使用字符串"www"來實例化該對象,接着循環調用append方法將本地變量i添加到 StringBuilder 對象中。

public class com.seaboat.string.TestString3 {
  public com.seaboat.string.TestString3();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #16                 // class java/lang/StringBuilder
       3: dup
       4: ldc           #18                 // String www
       6: invokespecial #20                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
       9: astore_1
      10: iconst_0
      11: istore_2
      12: goto          24
      15: aload_1
      16: iload_2
      17: invokevirtual #23                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      20: pop
      21: iinc          2, 1
      24: iload_2
      25: bipush        10
      27: if_icmplt     15
      30: return
}
複製代碼

 

 

 

 

 

 

發佈了101 篇原創文章 · 獲贊 126 · 訪問量 107萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章