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,需要使用開關進行管理,不能一直開啟。

 
 
 
參考文章:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM