駁《阿里「Java開發手冊」中的1個bug》?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前兩天寫了一篇關於"},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s?__biz=MzU1NTkwODE4Mw==&mid=2247486248&idx=1&sn=05a3d29f24000626cae0ad57857ee6b3&scene=21#wechat_redirect","title":null},"content":[{"type":"text","text":"《阿里Java開發手冊中的 1 個bug》"}]},{"type":"text","text":"的文章,評論區有點炸鍋了,基本分爲兩派,支持老王的和質疑老王的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先來說,無論是那一方,我都真誠的感謝你們。特別是「二師兄」,本來是打算週五晚上好好休息一下的(週五晚上發佈的文章),結果因爲和我討論這個問題,一直搞到晚上 12 點左右,可以看出,他對技術的那份癡迷。這一點我們是一樣的,和閱讀本文的你一樣,"},{"type":"text","marks":[{"type":"strong"}],"text":"我們屬於一類人,一類對技術無限癡迷的人"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/eb/eb484b7626b34330afbc868fb054c5bc.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"對與錯的意義"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實在準備發這篇文章時,已經預料到這種局面了,當你提出質疑時,無論對錯,一定有相反的聲音,因爲別人也有質疑的權利,而此刻你要做的,就是儘量保持冷靜,用客觀的態度去理解和驗證這些問題。而這些問題(不同的聲音)終將成爲一筆寶貴的財富,因爲你在這個驗證的過程中一定會有所收穫。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時我也希望我的理解是錯的,因爲和大家一樣,也是阿里《Java開發手冊》的忠實“信徒”,只是意外的窺見了“不同”,然後順手把自己的思路和成果分享給了大家。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但我也相信,任何“權威”都有犯錯的可能,老祖宗曾告訴過我們“人非聖賢孰能無過”。我倒不是非要糾結誰對誰錯,相反"},{"type":"text","marks":[{"type":"strong"}],"text":"我認爲一味的追求誰對誰錯是件非常幼稚的事情"},{"type":"text","text":",只有小孩子才這樣做,我們要做的是"},{"type":"text","marks":[{"type":"strong"}],"text":"通過辯論這件事的“對與錯”,學習到更多的知識,幫助我們更好的成長"},{"type":"text","text":",這纔是我今天這篇文章誕生的意義。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"喬布斯曾說過:"},{"type":"text","marks":[{"type":"strong"}],"text":"我最喜歡和聰明人一起工作,因爲完全不用顧忌他們的尊嚴"},{"type":"text","text":"。我倒不是聰明人,但我知道任何一件“錯事”的背後,一定有它的價值。因此我不怕被“打臉”,如果想要快速成長的話,我勸你也要這樣。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好了,就聊這麼多,接下來咱們進入今天正題。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"反對的聲音"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"持不同看法的朋友的主要觀點有以下這些:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/77/77e609d191b112cbba1b91bd300c1797.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a2/a261f573febec9f34487d1815e02c879.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我把這些意見整理了一下,其實說的是一件事,我們先來看原文的內容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在《Java開發手冊》泰山版(最新版)的第二章第三小節的第 4 條規範中指出:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【強制】在日誌輸出時,字符串變量之間的拼接使用佔位符的方式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"說明:因爲 String 字符串的拼接會使用 StringBuilder 的 append() 方式,有一定的性能損耗。使用佔位符僅 是替換動作,可以有效提升"},{"type":"text","marks":[{"type":"strong"}],"text":"性能"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正例:logger.debug(\"Processing trade with id: {} and symbol: {}\", id, symbol);"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"反對者(注意這個“反對者”不是貶義詞,而是爲了更好的區分角色)的意思是這樣的:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用佔位符會先判斷日誌的輸出級別再決定是否要進行拼接輸出,而直接使用 StringBuilder 的方式會先進行拼接再進行判斷,這樣的話,當日志級別設置的比較高時,因爲 StringBuilder 是先拼接再判斷的,因此造成系統資源的浪費,所以使用佔位符的方式比 StringBuilder 的方式性能要高。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"咱先放下反對者說的這個含義在阿里《Java開發手冊》中是否有體現,因爲我確實沒有看出來,咱們先順着這個思路來證實一下這個結論是否正確。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"性能評測"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還是老規矩,咱們用數據和代碼說話,爲了測試 JMH(測試工具)能和 Spring Boot 很好的結合,首先我們要做的就是先測試一下日誌輸出級別設置,是否能在 JMH 的測試代碼中生效。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼接下來我們先來編寫「日誌級別設置」的測試代碼:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import lombok.extern.slf4j.Slf4j;\nimport org.openjdk.jmh.annotations.*;\nimport org.openjdk.jmh.runner.Runner;\nimport org.openjdk.jmh.runner.RunnerException;\nimport org.openjdk.jmh.runner.options.Options;\nimport org.openjdk.jmh.runner.options.OptionsBuilder;\nimport org.springframework.boot.SpringApplication;\n\nimport java.util.concurrent.TimeUnit;\n\n\n@BenchmarkMode(Mode.AverageTime) // 測試完成時間\n@OutputTimeUnit(TimeUnit.NANOSECONDS)\n@Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) // 預熱 2 輪,每次 2s\n@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 測試 5 輪,每次 3s\n@Fork(1) // fork 1 個線程\n@State(Scope.Thread) // 每個測試線程一個實例\n@Slf4j\npublic class LogPrintAmend {\n public static void main(String[] args) throws RunnerException {\n // 啓動基準測試\n Options opt = new OptionsBuilder()\n .include(LogPrintAmend.class.getName() + \".*\") // 要導入的測試類\n .build();\n new Runner(opt).run(); // 執行測試\n }\n\n @Setup\n public void init() {\n // 啓動 spring boot\n SpringApplication.run(DemoApplication.class);\n }\n\n @Benchmark\n public void logPrint() {\n log.debug(\"show debug\");\n log.info(\"show info\");\n log.error(\"show error\");\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在測試代碼中,我們使用了 3 個級別的日誌輸出指令:"},{"type":"codeinline","content":[{"type":"text","text":"debug"}]},{"type":"text","text":" 級別、 "},{"type":"codeinline","content":[{"type":"text","text":"info"}]},{"type":"text","text":" 級別和 "},{"type":"codeinline","content":[{"type":"text","text":"error"}]},{"type":"text","text":" 級別。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後我們再在配置文件(application.properties)中的設置日誌的輸出級別,配置如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"logging.level.root=info"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看出我們把所有的日誌輸出級別設置成了 "},{"type":"codeinline","content":[{"type":"text","text":"info"}]},{"type":"text","text":" 級別,然後我們執行以上程序,執行結果如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/83/83a9a8a2c222e524ed7564a5bc89a027.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上圖中我們可以看出,日誌只輸出了 "},{"type":"codeinline","content":[{"type":"text","text":"info"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"error"}]},{"type":"text","text":" 級別,也就是說我們設置的日誌輸出級別生效了,爲了保證萬無一失,我們再把日誌的輸出級別降爲 "},{"type":"codeinline","content":[{"type":"text","text":"debug"}]},{"type":"text","text":" 級別,測試的結果如下圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/31/31ddacad3787c308f04205c1d6d8debf.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面的結果可以看出,我們設置的日誌級別沒有任何問題,也就是說,"},{"type":"text","marks":[{"type":"strong"}],"text":"JMH 框架可以很好的搭配 SpringBoot 來使用"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小貼士,日誌的等級權重爲:TRACE < DEBUG < INFO < WARN < ERROR < FATAL"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了上面日誌等級的設置基礎之後,我們來測試一下,如果先拼接字符串再判斷輸出的性能和佔位符的性能評測結果,完整的測試代碼如下:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import lombok.extern.slf4j.Slf4j;\nimport org.openjdk.jmh.annotations.*;\nimport org.openjdk.jmh.runner.Runner;\nimport org.openjdk.jmh.runner.RunnerException;\nimport org.openjdk.jmh.runner.options.Options;\nimport org.openjdk.jmh.runner.options.OptionsBuilder;\nimport org.springframework.boot.SpringApplication;\n\nimport java.util.concurrent.TimeUnit;\n\n\n@BenchmarkMode(Mode.AverageTime) // 測試完成時間\n@OutputTimeUnit(TimeUnit.NANOSECONDS)\n@Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) // 預熱 2 輪,每次 2s\n@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 測試 5 輪,每次 3s\n@Fork(1) // fork 1 個線程\n@State(Scope.Thread) // 每個測試線程一個實例\n@Slf4j\npublic class LogPrintAmend {\n\n private final static int MAX_FOR_COUNT = 100; // for 循環次數\n\n public static void main(String[] args) throws RunnerException {\n // 啓動基準測試\n Options opt = new OptionsBuilder()\n .include(LogPrintAmend.class.getName() + \".*\") // 要導入的測試類\n .build();\n new Runner(opt).run(); // 執行測試\n }\n\n @Setup\n public void init() {\n SpringApplication.run(DemoApplication.class);\n }\n\n @Benchmark\n public void appendLogPrint() {\n for (int i = 0; i < MAX_FOR_COUNT; i++) { // 循環的意圖是爲了放大性能測試效果\n // 先拼接\n StringBuilder sb = new StringBuilder();\n sb.append(\"Hello, \");\n sb.append(\"Java\");\n sb.append(\".\");\n sb.append(\"Hello, \");\n sb.append(\"Redis\");\n sb.append(\".\");\n sb.append(\"Hello, \");\n sb.append(\"MySQL\");\n sb.append(\".\");\n // 再判斷\n if (log.isInfoEnabled()) {\n log.info(sb.toString());\n }\n }\n }\n\n @Benchmark\n public void logPrint() {\n for (int i = 0; i < MAX_FOR_COUNT; i++) { // 循環的意圖是爲了放大性能測試效果\n log.info(\"Hello, {}.Hello, {}.Hello, {}.\", \"Java\", \"Redis\", \"MySQL\");\n }\n }\n}\n複製代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看出代碼中使用了 "},{"type":"codeinline","content":[{"type":"text","text":"info"}]},{"type":"text","text":" 的日誌數據級別,那麼此時我們再將配置文件中的日誌級別設置爲大於 "},{"type":"codeinline","content":[{"type":"text","text":"info"}]},{"type":"text","text":" 的 "},{"type":"codeinline","content":[{"type":"text","text":"error"}]},{"type":"text","text":" 級別,然後執行以上代碼,測試結果如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/75/751701de8efbe4ec8ba10256acef8c2b.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"哇,測試結果真令人滿意。從上面的結果可以看出使用佔位符的方式的性能,真的比 "},{"type":"codeinline","content":[{"type":"text","text":"StringBuilder"}]},{"type":"text","text":" 的方式高很多,這就說明阿里的《Java開發手冊》說的沒問題嘍。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"反轉"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但事情並沒有那麼簡單,就比如你正在路上走着,迎面而來了一個自行車,眼看就要撞到你了,此時你會怎麼做?毫無疑問你會下意識的躲開。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼對於上面的那個評測也是一樣,爲什麼要在字符串拼接之後再進行判斷呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果編程已經是你的一份正式職業,那麼先判斷再拼接字符串是最基礎的職業技能要求,這和你會下意識的躲開迎面相撞的自行車的道理是一樣的,在你完全有能力規避問題的時候,一定是先規避問題,再進行其他操作的,否則在團隊 review 代碼的時候或者月底裁員的時候時,你一定是首選的“受害”對象了。因爲像這麼簡單的(錯誤)問題,只有剛入門的新手纔可能會出現。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼按照一個程序最基本的要求,我們應該這樣寫代碼:"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import lombok.extern.slf4j.Slf4j;\nimport org.openjdk.jmh.annotations.*;\nimport org.openjdk.jmh.runner.Runner;\nimport org.openjdk.jmh.runner.RunnerException;\nimport org.openjdk.jmh.runner.options.Options;\nimport org.openjdk.jmh.runner.options.OptionsBuilder;\nimport org.springframework.boot.SpringApplication;\n\nimport java.util.concurrent.TimeUnit;\n\n\n@BenchmarkMode(Mode.AverageTime) // 測試完成時間\n@OutputTimeUnit(TimeUnit.NANOSECONDS)\n@Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) // 預熱 2 輪,每次 2s\n@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 測試 5 輪,每次 3s\n@Fork(1) // fork 1 個線程\n@State(Scope.Thread) // 每個測試線程一個實例\n@Slf4j\npublic class LogPrintAmend {\n\n private final static int MAX_FOR_COUNT = 100; // for 循環次數\n\n public static void main(String[] args) throws RunnerException {\n // 啓動基準測試\n Options opt = new OptionsBuilder()\n .include(LogPrintAmend.class.getName() + \".*\") // 要導入的測試類\n .build();\n new Runner(opt).run(); // 執行測試\n }\n\n @Setup\n public void init() {\n SpringApplication.run(DemoApplication.class);\n }\n\n @Benchmark\n public void appendLogPrint() {\n for (int i = 0; i < MAX_FOR_COUNT; i++) { // 循環的意圖是爲了放大性能測試效果\n // 再判斷\n if (log.isInfoEnabled()) {\n StringBuilder sb = new StringBuilder();\n sb.append(\"Hello, \");\n sb.append(\"Java\");\n sb.append(\".\");\n sb.append(\"Hello, \");\n sb.append(\"Redis\");\n sb.append(\".\");\n sb.append(\"Hello, \");\n sb.append(\"MySQL\");\n sb.append(\".\");\n log.info(sb.toString());\n }\n }\n }\n\n @Benchmark\n public void logPrint() {\n for (int i = 0; i < MAX_FOR_COUNT; i++) { // 循環的意圖是爲了放大性能測試效果\n log.info(\"Hello, {}.Hello, {}.Hello, {}.\", \"Java\", \"Redis\", \"MySQL\");\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"甚至是把 "},{"type":"codeinline","content":[{"type":"text","text":"if"}]},{"type":"text","text":" 判斷提到 "},{"type":"codeinline","content":[{"type":"text","text":"for"}]},{"type":"text","text":" 循環外,但本文的 "},{"type":"codeinline","content":[{"type":"text","text":"for"}]},{"type":"text","text":" 不代表具體的業務,而是爲了更好的放大測試效果而寫的代碼,因此我們會把判斷寫到 "},{"type":"codeinline","content":[{"type":"text","text":"for"}]},{"type":"text","text":" 循環內。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼此時我們再來執行測試的代碼,執行結果如下圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7d273392e7e9e3c29c5166f8e7fc039.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上述結果可以看出,使用先判斷再拼接字符串的方式還是要比使用佔位符的方式性能要高。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼,"},{"type":"text","marks":[{"type":"strong"}],"text":"我們依然沒有辦法證明阿里《Java開發手冊》中的佔位符性能高的結論"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我依舊保持我的看法,"},{"type":"text","marks":[{"type":"strong"}],"text":"使用佔位符而非字符串拼接,主要可以保證代碼的優雅性,可以在代碼中少些一些邏輯判斷,但這樣寫和性能無關"},{"type":"text","text":"。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"擴展知識:格式化日誌"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上面的評測過程中,我們發現日誌的輸出格式非常“亂”,那有沒有辦法可以格式化日誌呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答案是:有的,默認日誌的輸出效果如下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/24/24312cd6a555e1ea971b6e917e4dc363.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"格式化日誌可以通過配置 Spring Boot 中的 "},{"type":"codeinline","content":[{"type":"text","text":"logging.pattern.console"}]},{"type":"text","text":" 選項實現的,配置示例如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"logging.pattern.console=%d | %msg %n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"日誌的輸出結果如下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ee/ee8ab90aac7145e1cbe9406e7557537a.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看出,格式化日誌之後,內容簡潔多了,但千萬不能因爲簡潔,而遺漏輸出關鍵性的調試信息。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文我們測試了讀者提出質疑的字符串拼接和佔位符的性能評測,發現佔位符方式性能高的觀點依然無從考證,所以我們的基本看法還是,"},{"type":"text","marks":[{"type":"strong"}],"text":"使用佔位符的方式更加優雅,可以通過更少的代碼實現更多的功能;至於性能方面,只能說還不錯,但不能說很優秀"},{"type":"text","text":"。在文章的最後我們講了 Spring Boot 日誌格式化的知識,希望本文可以切實的幫助到你,也歡迎你在評論區留言和我互動。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"最後的話"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原創不易,都看到這了,點個「"},{"type":"text","marks":[{"type":"strong"}],"text":"贊"},{"type":"text","text":"」再走唄,這是對我最大的支持與鼓勵,謝謝你!"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章