System.out.printf使用以及注意點
一、System.out.printf格式化輸出
1、常用控制符
控制符 |
說明 |
%d |
按十進位制整型資料的實際長度輸出。 |
%ld |
輸出長整型資料。 |
%md |
m 為指定的輸出欄位的寬度。如果資料的位數小於 m,則左端補以空格,若大於 m,則按實際位數輸出。 |
%u |
輸出無符號整型(unsigned)。輸出無符號整型時也可以用 %d,這時是將無符號轉換成有符號數,然後輸出。但程式設計的時候最好不要這麼寫,因為這樣要進行一次轉換,使 CPU 多做一次無用功。 |
%c |
用來輸出一個字元。 |
%f |
用來輸出實數,包括單精度和雙精度,以小數形式輸出。不指定欄位寬度,由系統自動指定,整數部分全部輸出,小數部分輸出 6 位,超過 6 位的四捨五入。 |
%.mf |
輸出實數時小數點後保留 m 位,注意 m 前面有個點。 |
%o |
以八進位制整數形式輸出,這個就用得很少了,瞭解一下就行了。 |
%s |
用來輸出字串。用 %s 輸出字元串同前面直接輸出字串是一樣的。但是此時要先定義字元陣列或字元指標儲存或指向字串,這個稍後再講。 |
%x(或 %X 或 %#x 或 %#X) |
以十六進位制形式輸出整數,這個很重要。 |
程式碼演示:
public static void main(String[] args) { 2 //最常用的主要是三個:字串 %s, 整型%d, 浮點型保留小數位%.mf(m表示小數點後m位), \n表示換行符 3 System.out.printf("*學生資料*\n 姓名:%s\n 年齡:%d歲\n 考試成績(保留兩位小數): %.2f\n", "小明", 15, 98.456); 4 }
控制檯顯示:
另外System.out.printf有一定程度的輸出格式化效果
輸出結果:
如果 使用System.out.print(ln)格式就出現了明顯不同
2、示例程式碼
package system.out; public class Printf { public static void main(String[] args) { //%代表格式化 //f代表輸出浮點數,9代表輸出長度,如果浮點數長度不足,則補空格,如果浮點數長度超出,則按實際長度輸出,2代表保留小數點後幾位小數 System.out.printf("%9.2f",1111.3); System.out.println(); //-號代表向左對齊,預設向右對齊 System.out.printf("%-9.2f", 1111.3); System.out.println(); //+號代表顯示正負號 System.out.printf("%+9.2f", 1111.3); System.out.println(); //+-號代表顯示正負號,且向左對齊 System.out.printf("%+-9.2f", 1111.3); System.out.println(); //d代表輸出整數 System.out.printf("%4d",15); System.out.println(); //o代表輸出8進位制整數 System.out.printf("%-4o",15); System.out.println(); //x代表輸出16進位制整數 System.out.printf("%-4x",15); System.out.println(); //#x代表輸出帶有16進位制標誌的整數 System.out.printf("%#x",15); System.out.println(); //s代表輸出字串 System.out.printf("%-8s", "我們是中心"); System.out.println(); //x$,整數加$表示第幾個變數,如果不加,變數按預設順序排列 System.out.printf("%2$-5s:奪得世界盃總冠軍,進球數:%1$3d,對方進球:%3$2d", 4,"法國",2); } }
二、看下底層程式碼實現
public PrintStream printf(String format, Object ... args) { return format(format, args); }
public PrintStream format(String format, Object ... args) { try { synchronized (this) { ensureOpen(); if ((formatter == null) || (formatter.locale() != Locale.getDefault())) formatter = new Formatter((Appendable) this); formatter.format(Locale.getDefault(), format, args); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } return this; }
可以看到和String.format底層實現類似呼叫Formatter()類的format方法。
public static String format(String format, Object... args) { return new Formatter().format(format, args).toString(); }
三、接下來分析下 String.format與StringBuilder與String +比較
1、測試程式碼:
class StringTest { public static void main(String[] args) { // testOperatorPlus(); //testAppend(); testFormat(); } private static void testFormat() { Runtime runtime = Runtime.getRuntime(); long memory; long prev_time; int i; long time; StringBuilder sb = new StringBuilder(); memory = runtime.freeMemory(); prev_time = System.currentTimeMillis(); for (i = 0; i < 10000; i++) { String s = String.format("Blah %d Blah %d Blah %d", i, i, i); } long ww=runtime.freeMemory(); time = System.currentTimeMillis() - prev_time; memory = memory - ww; System.out.println("Time: " + time + " Memory Usage: " + memory); } private static void testAppend() { Runtime runtime = Runtime.getRuntime(); long memory; long prev_time; int i; long time; StringBuilder sb = new StringBuilder(); memory = runtime.freeMemory(); prev_time = System.currentTimeMillis(); for (i = 0; i < 10000; i++) { sb.append("Blah "); sb.append(i); sb.append("Blah "); sb.append(i); sb.append("Blah "); sb.append(i); } time = System.currentTimeMillis() - prev_time; memory = memory - runtime.freeMemory(); System.out.println("Time: " + time + " Memory Usage: " + memory); } private static void testOperatorPlus() { Runtime runtime = Runtime.getRuntime(); long memory; long prev_time; int i; long time; StringBuilder sb = new StringBuilder(); memory = runtime.freeMemory(); prev_time = System.currentTimeMillis(); for (i = 0; i < 1000000; i++) { String s = "Blah " + i + "Blah " + i + "Blah " + i; } time = System.currentTimeMillis() - prev_time; memory = memory - runtime.freeMemory(); System.out.println("Time: " + time + " Memory Usage: " + memory); } }View Code
結果如下
Method | Time(ms) | Memory Usage(long) |
‘+’ operator | 102 | 44053736 |
StringBuilder.append | 6 | 884768 |
String.foramt | 110 | 22639000 |
可以看到
StringBuilder.append
的執行時間和記憶體佔用都是最優的。'+'運算子比直接呼叫StringBuilder.append
要慢上不少,特別是要連線的字串數量較多時,記憶體佔用也特別大。String.format
由於每次都有生成一個Formatter
物件,較慢也是情理之中。
分析下String.format原始碼可以看到底層也用到了StringBuilder
public static String format(String format, Object... args) { return new Formatter().format(format, args).toString(); }
public Formatter() { this(Locale.getDefault(Locale.Category.FORMAT), new StringBuilder()); }
四、由此引發的優化探討
在編碼中 System.out.println將物件結果輸出到控制檯,會花費大量的CPU資源,因此釋出的程式碼中不要包含System.out.println 或 System.out.printf。
使用日誌框架代替,生產環境注意控制輸出級別。
即便使用日誌框架也要注意輸出編碼方式,例如 反例(不要這麼做):logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
字串拼接,這樣會產生很多String物件,佔用空間,影響效能。
另外關於日誌輸出其他建議:
1、使用[]進行引數變數隔離
如有引數變數,應該寫成如下寫法:
logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);這樣的格式寫法,可讀性更好,對於排查問題更有幫助。 2、並不是所有的service都進行出入口打點記錄,單一、簡單service是沒有意義的(job除外,job需要記錄開始和結束,)。 反例(不要這麼做):
public List listByBaseType(Integer baseTypeId) { log.info("開始查詢基地"); BaseExample ex=new BaseExample(); BaseExample.Criteria ctr = ex.createCriteria(); ctr.andIsDeleteEqualTo(IsDelete.USE.getValue()); Optionals.doIfPresent(baseTypeId, ctr::andBaseTypeIdEqualTo); log.info("查詢基地結束"); return baseRepository.selectByExample(ex); }
對於複雜的業務邏輯,需要進行日誌打點,以及埋點記錄,比如電商系統中的下訂單邏輯,以及OrderAction操作(業務狀態變更)。
如果所有的service為SOA架構,那麼可以看成是一個外部介面提供方,那麼必須記錄入參。呼叫其他第三方服務時,所有的出參和入參是必須要記錄的(因為你很難追溯第三方模組發生的問題)
3、 生產環境需要關閉DEBUG資訊
如果在生產情況下需要開啟DEBUG,需要使用開關進行管理,不能一直開啟。
參考文章: https://www.jianshu.com/p/7eb224b9f064&n