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
此時代碼需要如此調用:
@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. 性能對比
上圖是 分別循環 100000, 500000, 1000000, 2000000 統計出來的數據
在性能上 String format 是最差的
字符串 ++ 最快, StringBuilder 次之 , slf4j 的格式化再次之
測試硬件概覽:
型號名稱: MacBook Pro
處理器名稱: Intel Core i5
處理器速度: 2.9 GHz
內存: 16 GB
10. 參考
-
commons-lang3 官方地址: http://commons.apache.org/proper/commons-lang/download_lang.cgi
轉載:
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
}
複製代碼