搬磚者:為什麼程序總是那麼慢?它現在到底在幹什麼?時間都花到哪裡去了?
面試官:簡單談談 Java 程序性能優化?
1.字符串處理優化,乃優化之源。
研發過程中,String 的 API 用的應該是最多,創建 String 對象,以及字符串分割處理那是常有的事兒。
1.1. 字符串分割,誰更勝一籌?
字符串分割,常用的方式有哪些?哪種方式好一些?
方式一,經常用 String 提供的 split() 方法來滿足業務需求。
![Java 程序性能如何調優?技巧篇](http://p2.ttnews.xyz/loading.gif)
代碼模擬了一些數據,然後程序跑起來,花費大約 3000 多毫秒。
方式二,採用字符串分割的工具類 StringTokenizer。
![Java 程序性能如何調優?技巧篇](http://p2.ttnews.xyz/loading.gif)
採用 StringTokenizer 完成 split() 同樣的數據分割,花費大約 500 毫秒。
從運行效果, StringTokenizer 其效率高於 split() 方法。所以,在能夠使用 StringTokenizer 進行處理的地方,就儘量使用 StringTokenizer 進行字符串分割處理。
另外,平時研發中,需要注意一點,在使用索引訪問用 String 的 split() 方法得到的數組時,需做最後一個分隔符後有無內容的檢查,否則會有拋異常的風險。
1.2. 字符串拼接,哪種方式更優?
方式一,使用 + 號拼接字符串。
程序跑起來,大約花費 27687 毫秒。
方式二,使用 StringBuilder 進行拼接字符串。
程序跑起來,大約花費 24 毫秒。
很顯然,使用 + 號拼接字符串,其效率明顯較低,採用 StringBuilder 來完成字符串連接性能相對較好,同理,如果需要考慮線程安全的情況下,可以選擇 StringBuffer。
另外,在阿里開發手冊中也強烈推薦,在循環體內,字符串的連接方式,使用 StringBuilder 的 append 方法進行擴展。如果採取 + 號拼接,會造成內存資源浪費。
2.善用 arraycopy(),讓數組複製不再難。
數組複製是研發過程中,使用較多的功能,JDK 中提供了 API 來實現。但是,哪種方式較好呢?
方式一,作為開發人員,沒事就喜歡造輪子,代碼會這麼寫。
方式二,採取 System.arraycopy() 來完成數組複製。
紅色圈住部分,在本機跑起來驗證,方式一、方式二,差距不是特別大,在數據量大的時候,System.arraycopy() 還是稍微好一點。
方式三,採取 Arrays.copyOf() 來完成數組的複製。
鑑於 Arrays.copyOf() 底層還是調用 System.arraycopy() 來實現,性能肯定稍遜色於直接調用 System.arraycopy() 來完成數組複製。
所以,對數組的操作,如果能用 System.arraycopy() 這個方法實現,建議儘量去使用。
3.關注循環體,別做重複勞動。
儘可能讓程序少做重複的計算,尤其要重點關注循環體內的代碼。
舉個簡單的栗子,上面代碼段中,Math.PI * Math.sin(k) 的執行在循環體中重複執行,而且執行結果是唯一的,可以考慮提到循環體外。
本機進行驗證時,前者大約花費 30 毫秒,而調整後,大約花費 2 毫秒,性能提升還是有的。
所以,從循環體內提取重複的代碼,可以有效的提升系統性能。
另外,在阿里開發手冊中強烈推薦,循環體中的語句要考量性能,以下操作儘量移至循環體外處理,如定義對象、變量、獲取數據庫連接,進行不必要的 try-catch 操作(這個 try-catch 是否可以移至循環體外)。
4.集合用的多,使用場景要注意。
業務研發中,集合家族的 API 使用頻率相當之高。那麼,充分的選擇好數據結構進行數據存儲,便是最好的程序優化。
為了更清晰的說清各自的使用場景,也為了更好的助你掌握,梳理成思維導圖。
4.1. List 家族,誰能得寵?
4.2. Map 家族,誰佔鰲頭?
另外,在集合初始化時,要指定集合初始值大小。
說明:HashMap 使用 HashMap(int initialCapacity) 初始化。
正例: initialCapacity = (需要存儲的元素個數 / 負載因子) + 1。
注意負載因子(即 loader factor)默認為 0.75,如果暫時無法確定初始值大小,請設置為 16(即默認值)。
反例:HashMap 需要放置 1024 個元素,由於沒有設置容量初始大小,隨著元素不斷增加,容 量 7 次被迫擴大,resize 需要重建 hash 表,嚴重影響性能。
—— 阿里開發手冊
4.2. Set 家族,誰最適配?
圖中已經把常用的數據結構列舉了出來,就不再一一去寫代碼驗證啦。還是那句話,選擇好數據結構進行數據存儲,便是最好的程序優化。
5.緩衝,讓子彈飛一會兒。
緩衝,最常用的場景就是提高 I/O 的速度,解決 I/O 性能瓶頸。在
Java 中對不少 I/O 組件都提供了緩衝功能。
例如,採用 FileWriter 向文件中寫入數據。
程序跑起來,花費大約 8212 毫秒。那麼,再來看看加入緩衝之後會有什麼效果?
程序跑起來,花費大約 4143 毫秒,性能提升了一倍。
所以,文件讀寫操作時儘量都使用緩衝流進行操作,有助於提升性能。
6.緩存,讓坐飛機的和坐驢車的打交道。
在實際項目研發中,緩存也是經常使用到,緩存是為了提升系統性能而開闢的內存空間。
最為簡單的緩存可以直接使用 HashMap 實現,例如應用的配置信息,在啟動的時候都加載進去。
針對銀行編碼等一些使用頻率較高的業務數據,或者來之不易的計算結果,都可以保存到緩存中,當再次使用時,直接從緩存中獲取,而不需要再佔用寶貴的系統資源。
目前有很多基於 Java 的緩存框架,而我用的最多的是 EhCache。
7.日誌記的好,線上沒煩惱。
推薦:謹慎地記錄日誌。生產環境禁止輸出 debug 日誌;有選擇地輸出 info 日誌;如果使 用 warn 來記錄剛上線時的業務行為信息,一定要注意日誌輸出量的問題,避免把服務器磁盤撐爆,並記得及時刪除這些觀察日誌。
說明:大量地輸出無效日誌,不利於系統性能提升,也不利於快速定位錯誤點。
記錄日誌時請思考:這些日誌真的有人看嗎?看到這條日誌你能做什麼?能不能給問題排查帶來好處?
—— 阿里開發手冊
8.雜七雜八,囉嗦一句不嫌多。
一個優化原則。先實現業務功能,再考慮優化性能,如果功能都沒實現,談其它的都白扯。
一個調優思路。
- 首先從系統設計層面,去看看是否有改進的可能,是不是可以引入一些設計模式、是不是可以引入緩存機制等方法,來屏蔽潛在的性能問題。
- 然後從代碼層面,看看代碼是否有優化的可能。
- 接著去看看 Java 程序運行的環境,也就是通過調整 JVM 的參數來提升一下性能。
- 接著到數據庫層面,看看是否有調優的可能。
- 最後到操作系統層面,看看是否可以進行調優。
好了,本次的分享,就到這裡,希望你們喜歡,更多精彩歡迎關注「一猿小講」。
閱讀更多 一猿小講 的文章