Java效能優化一:設計優化和程式優化,開發必備優化技巧!
現代大規模關鍵性系統中的Java效能調優,是一項富有挑戰的任務。你需要關注各種問題,包括演算法結構、記憶體分配模式以及磁碟和檔案I/O的使用方式。效能調優最困難的通常是找到問題所在,即便是經驗豐富的人也會被他們的直覺所誤導。效能殺手總是隱藏在最意想不到的地方。
Java效能問題一直困擾著廣大程式設計師,由於平臺複雜性,要定位問題,找出其根源確實很難。隨著10多年Java平臺的改進以及新出現的多核多處理器,Java軟體的效能和擴充套件性已經今非昔比了。現代JVM持續演進,內建了更為成熟的優化技術、執行時技術和垃圾收集器。與此同時,底層的硬體平臺和作業系統也在演化。
一、設計優化
1、並行代替序列。
2、時間換空間:不引入中間變數實現兩個數字的交換。代價是增加 CPU 運算。
3、空間換時間:使用下標陣列排序。
4、Java 中的緩衝區:
(1)緩衝最常用的場景就是提高 IO 速度:比如 BufferedWriter 可以用來裝飾 FileWriter ,為 FileWriter 加上緩衝。 BufferedOutputStream 可以用來裝飾 FileOutputStream 。使用這兩個裝飾器時候可以指定緩衝區大小,預設的 size 為 8K 。
(2)JavaNIO 中的各種 Buffer 類族,有更加強大的緩衝區控制功能。
(3)除了效能上的優化,緩衝區也可以作為上層元件和下層元件的一種通訊工具,將上層元件好下層元件進行解耦。比如生產者消費者模式中的緩衝區。
5、快取:
(1)比如 Hibernate 採用的兩級快取:一級快取和二級快取。二級快取指的是 sessionFactory層面上的快取, Hibernate 採用的是 EHCache 。一級快取指的是 session 層面上的快取。
6、善於利用 Java 中的設計模式:享元模式、代理模式、裝飾器模式等。
7、物件複用技術 -- 池的使用
(1)資料庫連線池:較常使用的資料庫連線池元件是 C3P0 和 Proxool 。其中 C3P0 是伴隨 Hibernate 一起釋出的, Hibernate 預設採用的資料庫連線池。
(2)執行緒池:自定義執行緒池以及 jdk1.5 提供的執行緒池元件。
二、程式優化
常用的程式設計優化技巧:
1 、有助於改善系統性能的技巧:
(1) 、慎用異常: for 迴圈中使用 try-catch 會大大降低系統性能
(2) 、使用區域性變數:區域性變數的訪問速度遠遠高於類的靜態變數的訪問速度,因為類的 變數是存在在堆空間中的。
(3) 、位運算代替乘除法:右移代表除以二、左移代表乘以二。
(4) 、有的時候考慮是否可以使用陣列代替位運算。
(5) 、一維陣列代替二維陣列。
(6) 、提取表示式:儘可能讓程式少做重複的計算,尤其要關注在迴圈體的程式碼,從迴圈提中提取重複的程式碼可以有效的提升系統性能。
2、字串優化處理
(1)String 類的特點:不變性、針對常量池的優化( String.intern() 方法的意義)
(2)subString 方法的記憶體洩漏 :
(3)字串分割和查詢不要使用 split 函式,效率低,而是使用 StringTokenizer 或者 indexOf結合 subString() 函式完成分割。
(4)用 charAt ()方法代替 startWith ()方法。
(5)對於靜態字串或者變數字串的連線操作, Java 在編譯的時候會進行徹底的優化,將多個連線操作的字串在編譯時合成一個單獨的字串,而不是生成大量的 String 例項。只生成一個物件。
(6)在無需考慮執行緒安全情況下儘量使用 StringBuilder 。
(7)StringBuffer 和 StringBuilder 初始化的時候都可以設定一個初始值,預設是 16B 。如果字串的長度大於 16B 的時候,則需要進行擴容。擴容策略是將原有的容量大小翻倍,以新的容量申請記憶體空間,建立 char 陣列,然後將陣列中的內容複製到這個新的陣列中,使用 Arrays.copyOf() 函式。因此,如果能預先評估 StringBuilder 的大小,則可以節省這些複製操作,從而提高系統的效能。
3 、引用型別:強、軟、若、虛四種引用型別。
WeakHashMap :是弱引用的一中典型應用,它使用弱引用作為內部資料的儲存方案。可以作為簡單的快取表解決方案。
如果在系統中,需要一張很大的 Map 表, Map 中的表項作為快取之用。這也意味著即使沒能從該 Map 中取得相應地資料,系統也可以通過選項方案獲取這些資料,雖然這樣會消耗更多的時間,但是不影響系統的正常執行。這個時候,使用 WeakHashMap 是最合適的。因為 WeakHashMap 會在系統記憶體範圍內,儲存所有表項,而一旦記憶體不夠,在 GC 時,沒有被引用的又會很快被清除掉,避免系統記憶體溢位。
4、直接記憶體訪問: DirectBuffer 直接分配在實體記憶體中,並不佔用對空間,因此也不受對空間限制。 DirectBuffer 的讀寫操作比普通 Buffer 塊,因為 DirectBuffer 直接操縱的就是核心緩衝區。
5、List 介面
( 1 ) ArrayList 和 Vector 的區別:它們幾乎使用了相同的演算法,它們的唯一區別是對多執行緒的支援。 ArrayList 是不安全的,而 Vector 是執行緒安全的。
( 2 ) LinkedList 和 ArrayList 的區別:
a 、 linkedList 採用連結串列實現,適合於資料刪除和插入非常頻繁的情況,不適合隨機訪問。
b 、 ArrayList 採用陣列實現,適用於隨機查詢和順序讀的情況,不適合刪除和插 入資料非常頻繁的場景。
(3)基於陣列的 List 都會有一個容量引數。當 ArrayList 所儲存的元素容量超過其已有大小的時候就會進行擴容,陣列的擴容會導致整個陣列進行一次記憶體複製。因此合理的陣列大小會減小陣列擴容的次數從而提高系統性能。
(4)遍歷列表的時候儘量使用迭代器,速度塊。
6、Map 介面:
(1)HashMap 的實現原理:簡單的說, HashMap 就是將 key 做 hash 演算法,然後將 hash 值對映到記憶體地址,直接取得 key 所對應的資料。在 HashMap 中,底層資料結構使用的是陣列,所謂的記憶體地址指的是陣列的下標索引。
(2)容量引數與擴容:預設情況下, hashmap 的初始容量為 16 ,負載因子為 0.75 ,也就是說當 hashmap 的實際容量達到了初始容量 * 負載因子( hashmap 內部維護的一個 threshold 值)的時候, hashmap 就會進行擴容。在擴容時,會遍歷整個 hashmap ,因此應該設定合理的初始大小和負載因子,可以減小 hashmap 擴容的次數。
(3)LinkedHashMap-- 有序的 HashMap : HashMap 的最大缺點是其無序性,被存入到 Hashmap 中的元素,在遍歷 HashMap 的時候,其輸出不一定按照輸入的順序,而是 HashMap 會根據 hash 演算法設定一個查詢高效的順序。如果希望儲存輸入順序,則需要使用 LinkedHashMap 。LinkedHashmap 在內部又增加了一個連結串列,用於儲存元素的順序。
(4)LinkedList 可以提供兩種型別的順序:一個是元素插入時候的順序,一個是最近訪問的順序。注意: LinkedHashMap 在迭代過程中,如果設定為按照最後訪問時間進行排序,即:每當使用 get() 方法訪問某個元素時,該元素便會移動到連結串列的尾端。但是這個時候會出現異常,因此, LinkedHashMap 工作在這種模式的時候,不能在迭代器中使用 get() 操作。
(5)關於 ConcurrentModificationException :該異常一般會在集合迭代過程中被修改時丟擲。因此,不要在迭代器模式中修改集合的結構。這個特性適合於所有的集合類,包括 HashMap 、 Vector 、 ArrayList 等。
(6)TreeMap-- 如果要對元素進行排序,則使用 TreeMap 對 key 實現自定義排序,有兩種方式:在 TreeMap 的建構函式中注入一個 Comparator 或者使用一個實現了 Comparable 的 key 。
(7)如果需要將排序功能加入 HashMap ,最好是使用 Treemap 而不是在應用程式自定義排序。
(8)HashMap 基於 Hash 表實現, TreeMap 基於紅黑樹實現。
7 、 Map 和 Set 的關係:
( 1 )所有 Set 的實現都只是對應的 Map 的一種封裝,其內部維護一個 Map 物件。即: Set只是相應的 Map 的 Value 是一種特殊的表現形式的一種特例。
( 2 ) Set 主要有三種實現類: HashSet 、 LinkedHashSet 、 TreeSet 。其中 HashSet 是基於 Hash 的快速元素插入,元素之間無序。 LinkedHashSet 同時維護著元素插入順序,遍歷集合的時候,總是按照先進先出的順序排序。 TreeSet 是基於紅黑樹的實現,有著高效的基於元素 Key 的排序演算法。
8、優化集合訪問程式碼:
( 1 )、分離迴圈中被重複呼叫的程式碼:例如, for 迴圈中使用集合的 size() 函式,則不應該把這個函式的呼叫放到迴圈中,而是放到迴圈外邊、
( 2 )、省略相同的操作:
9、 RandomAccess 介面:通過 RandomAccess 可知道 List 是否支援隨機快速訪問。同時,如果應用程式需要通過索引下標對 List 做隨機訪問,儘量 buyaoshiyongLinkedList , ArrayList 或者 Vector 可以。
10 、 JavaNIO 的特性:
(1) 、為所有的原始型別提供 Buffer 支援。
(2) 、使用 Java.nio.charset.Charset 作為字元編碼解碼解決方案。
(3) 、增加通道抽象代替原有的 IO 流抽象。
(4) 、支援鎖和記憶體對映檔案的檔案訪問介面。
(5) 、提供基於 Selector 的非同步網路 IO 。
(8) 、 Buffer 的基本原理:
11 、 Buffer 的建立: Buffer 的靜態 allocate(int size) 方法或者 Buffer.wrap(byte[]src) 。
12 、 Buffer 的工作原理:三個變數: position ,代表當前緩衝區的位置,寫緩衝區的時候,將從 position 的下一個位置寫資料。 Capacity ,代表緩衝區的總容量上限。 Limit ,緩衝區的實際上限,也就是說,讀資料的時候,資料即是從 position 到 limit 之間的資料
13 、 flip 操作: limit=position,position=0, 一般是在讀寫切換的時候使用。寫完資料之後,需要限定下有效資料範圍,才能讀資料;
14 、 clear 操作: position-0 , limit=capacity. 。為重新寫入緩衝區做準備。
15 、 rewind 操作: position=0 ,為讀取緩衝區中有效資料做準備,一半 limit 已經被合理設定。
16、讀寫緩衝區:
(1) 、 public byte get() :順序讀取緩衝區的一個位元組, position 會加一
(2) 、 public Buffer get(byte[]dst): 將緩衝區中的資料讀入到陣列 dst 中,並適當的移動 position
(3) 、 public byte get(int index) :得到第 index 個位元組,但不移動 posoiion
(4) 、 public ByteBuffer put(byte b) :將位元組 b 放入到緩衝區中,並移動 position
(5) 、 public ByteBuffer put(int index,byte b) :將位元組 b 放到緩衝區的 index 位位置
(6) 、 pubglic final ByteBuffer(byte[]src) :將位元組陣列 src 放到緩衝區中。
17 、標誌緩衝區:類似於一個書籤的功能,在資料的處理過程中,可隨時記錄當前位置。然後在任意時刻,回到這個位置。 Mark 用於記錄當前位置, reset 用於恢復到 mark 所在的位置、
18 、複製緩衝區:使用 Buffer 的 duplicate 方法可以複製一個緩衝區,副本緩衝區和原緩衝區共享一份空間但是有有著獨立的 position 、 capacity 和 limit 值。
19 、緩衝區分片:緩衝區分片使用 slice 方法實現。它將在現有的緩衝區中,建立的子緩衝區。子緩衝區和父緩衝區共享資料。這個方法有助於將系統模組化。緩衝區切片可以將一個大緩衝區進行分割處理,得到的子緩衝區都具有緩衝的緩衝區模型結構;因此。這個操作有助於系統的模組化。
20 、只讀緩衝區:只讀緩衝區可以保證核心資料的安全,如果不希望資料被隨意篡改,返回一個只讀緩衝區是很有幫助的。
21、檔案對映到記憶體: NIO 提供了一種將檔案對映到記憶體的方法進行 IO 操作,這種方法比基於流 IO 快很多。這個操作主要由 FileChanne.map() 操作。使用檔案記憶體的方式,將文字通過 FileChannel 對映到記憶體中。然後從記憶體中讀取資料。同時,通過修改 Buffer, 將對記憶體中資料的修改寫到對應的硬碟檔案中。
22、處理結構化資料:散射和聚集。散射就是將資料讀入到一組 bytebuffer 中,而聚集正好相反。通過 ScatteringByteChannel 和 GatheringByteChannel 可以簡化對結構資料的操作。
以上只是一部分的效能優化,還有一部分將在下一篇文章寫到。好了,本篇文章就分享到這裡了。有興趣的新手夥伴們可以關注收藏起來,以後需要的時候可以多看看。如果有正在學java的程式設計師,可來我們的java技術學習扣qun哦:59789,1510裡面免費送java的視訊系統教程!