【進階之路】Java代碼性能調優(一)

導言

你們好,我是南橘,從接觸java到如今也有差很少兩年時間了,兩年時間,從一名連java有幾種數據結構都不懂超級小白,到如今懂了一點點的進階小白,學到了很多的東西。知識越分享越值錢,我這段時間總結(包括從別的大佬那邊學習,引用)了一些日常學習和麪試中的重點(自我認爲),但願給你們帶來一些幫助html

第一件事仍是把思惟導圖貼給你們,由於用的是免費版,因此有水印,若是須要原始版本的話,能夠加個人微信:java

1、字符串的優化

一、String優化

 String對象是java中重要的數據類型,在大部分狀況下咱們都會用到String。在java語言漫長的進化過程當中,開發人員也對String作了大量的優化,其中字符串的不變性和常量池複用也是String的重要特色  面試

  • 一、不變性

String類以final進行了修飾,在系統中就不可能有String的子類,同時String對象的狀態在其被建立以後就不在發生變化。在一個對象被多線程共享,並且被頻繁的訪問時,能夠省略同步和鎖的時間,從而提升性能。它也保證 hash 屬性值不會頻繁變動,確保了惟一性,使得相似 HashMap 容器才能實現相應的 key-value 緩存功能。因此這一點也是出於對系統安全性的考慮。正則表達式

  • 二、常量池優化

當兩個String對象擁有同一個值的時候,他們都只是引用了常量池中的同一個拷貝。因此當程序中某個字符串頻繁出現時,這個優化技術就能夠節省大幅度的內存空間了。算法

你們都知道
String a ="abc";
String b ="abc";
a == b
複製代碼

既然如此,那爲何在String中還存在「+」之類的操做呢?數組

二、字符串拼接

String經過+號來拼接字符串的時候,若是有字符串變量參與,實際上底層會轉成經過StringBuilder的append( )方法來實現。緩存

咱們再繼續分析"+",StringBuilder和StringBuffer的運行效率:安全

經過上面的例子咱們能夠看出,使用+號拼接字符串,其效率明顯低於使用StringBuffer和StringBuilder的append()方法進行拼接。同時StringBuffer的效率比StringBuilder低些,這是因爲StringBuffer實現了線程安全,效率較低也是不可避免的。因此在字符串的累加操做中,建議結合線程問題選擇,應避免使用+號拼接字符串性能優化

StringBuffer和StringBuilder的是對String的封裝,String是對char數組的封裝。是數組就有大小,就有不夠用的時候,不夠用只能擴容,也就是把原來的再複製到新的數組中。合適的容量參數天然可以減小擴容的次數,達到提升效率的目的bash

三、數據類型轉換

咱們在開發的過程當中應該知道,要儘可能使用toString()方法而不是使用String.valueOf()方法進行轉化。why?

從這邊的代碼就能看出來,String.valueOf()直接調用了底層的obj.toString()方法,不過在這以前會先判斷是否爲空。 因此,在大多數場景,能夠直接使用toString()方法就直接使用吧。

四、intern方法

大多數狀況,字符串是應用中佔用內存最多的一部分。虛擬機提供了字符串池,用於存放公共的字符串。能夠調用String.intern方法,返回一個字符串池中一樣內容的字符串,不過這種方調用是耗時的。

JVM提供了一個新的特性,在虛擬機中添加以下參數能夠開啓消除重複字符串的功能:

-xx:+UseG1GC -XX:+UseStringDeduplication

JVM將嘗試在垃圾收集過程當中消除重複的字符串。在垃圾收集過程當中,JVM會檢查內存中全部的對象,識別重複字符串並嘗試消除它。UseStringDeduplication不會消除重複的字符串對象自己,它只替換了底層的char[]。

五、其餘字符串優化的關注點

除了以前那些比較明顯的修改點,其實字符串優化中還有很多須要注意的地方。

  • 一、字符串變量和字符串常量equals的時候將字符串常量寫在前面

這一點很好理解,防止變量的值爲空出現空指針異常。

  • 二、儘可能重用對象

String對象的使用,出現字符串鏈接時應該使用StringBuilder/StringBuffer代替。因爲Java虛擬機不只要花時間生成對象,之後可能還須要花時間對這些對象進行垃圾回收和處理,所以,生成過多的對象將會給程序的性能帶來很大的影響。

  • 三、字符串分割與查找

原始的String.split()方法使用簡單,功能強大,支持正則表達式,可是,在性能敏感的系統中頻繁的使用這個方法是不可取的。咱們可使用效率更高的StringTokenizer類分割字符串。

其中str是要分割的字符串,delim是分割符,returnDelims是否返回分隔符,默認false。

  • 四、在初始化時,容量參數默認是16個字節。在構造方法中指定容量參數,減小擴容次數。

2、數字優化

一、數字裝箱

Java中,將原始的數字類型轉換爲對應的Number對象的機制叫作裝箱。將Number對象轉化爲對應原始類的機制叫作拆箱。在Java拆箱和裝箱的機制是自動完成的。

int被裝箱爲Integer,在性能方面是要付出一些代價的,JDK爲了不每次int類型裝箱都須要建立一個新的Integer對象,內部使用了緩存,其代碼以下:

IntegerCache的cache是一個Integer數組,默認保存了int值從-128到127的全部的Integer對象。

private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
複製代碼

其中high的值默認是127,能夠經過-XX: AutoBoxCacheMax =? 進行調整。

裝箱對性能的影響不是很大,但建立過多的對象會加大垃圾回收的復旦。有不少開源工具提供了避免自動裝箱的int專有集合類,好比著名的開源工具Jodd,提供了IntHashMap類、IntArrayList類。

二、金額計算

浮點型變量在進行計算的時候會出現丟失精度的問題。

System.out.println(0.05+2.01); -->2.0599999999999996

進行商品價格計算的時候,出現這種問題每每會致使很嚴重的事物,好比下單的時候帳單不正確致使沒法下單,或者出現對帳問題。

一般有兩種辦法來解決這個問題,一是用long來表示金額(以分爲單位),這是效率最高的,二是使用BigDecimal來解決這類問題。

BigDecimal能保證精度,但計算會有必定的性能影響,可是差距不是特別大。因此在項目中,若是涉及精度結算,能夠考慮使用BigDecimal,也可使用long。在分佈式或者微服務場景中,考慮到序列化與反序列化,long也是能夠被全部的序列化框架識別的。

3、集合的優化

首先把集合的繼承圖擺上來。

  • List和Set繼承自Collection接口。
  • Set無序不容許元素重複。
  • HashSet和TreeSet是兩個主要的實現類。
  • List有序且容許元素重複,支持null對象。ArrayList、LinkedList和Vector是三個主要的實現類。

  • Map也屬於集合系統,但和Collection接口不要緊。Map是key對value的映射集合,其中key列就是一個集合。key不能重複,可是value能夠重複。HashMap、TreeMap和Hashtable是三個主要的實現類。

對集合的優化,更多的實際上是在適合的狀況使用適合的數據結構,與字符串不一樣,對於集合來講,不一樣的數據結構之間的差別是很是巨大的。

一、ArrayList與LinkList

I、在知道初始值大小的狀況下儘可能賦上初始值大小。

看源碼就會發現,構造具備指定初始容量的空列表事實上是初始化一個空的數組列表,拿ArrayList來講,咱們都知道它的底層是用數組進行存儲的,它的默認大小是10,若是沒有根據預期來設置一個初始值大小,那麼它就會在使用過程當中不斷地擴容(如下爲擴容方法),每次擴容大小是1.5倍。

II、ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。對於隨機訪問get和set,ArrayList以爲優於LinkedList,由於LinkedList要移動指針。

III、對於新增和刪除操做add和remove,LinedList比較佔優點,由於ArrayList要移動數據。

二、Map

I、HashTable和HashMap都是基於鏈表+數組實現的(HashMap還有紅黑樹)。HashTable作了同步操做,HashMap沒有,所以HashMap是線程不安全類。HashTable的key和Value是不容許存Null的。HashMap的底層是native+位運算實現的,所以效率很高。

II、HashMap是無序的,LinkedHashMap是有序的,它有2種排序方式,一種是基於存儲順序,另外一種是基於訪問順序。

III、TreeMap是基於紅黑樹實現的,平衡查找樹查找效率優於平衡二叉樹。它不一樣於LinkedHashMap,它是根據key來排序的,使用TreeMap必須實現Comparable或在構造器中注入Comparator。若是須要排序,使用TreeMap的效率更高。

三、Set

I、Set的特色就是不容許有重複元素,HashMap封裝爲HashSet、LinkedHashMap封裝爲LinkedHashSet、TreeMap封裝爲TreeSet。比起封裝前的類,Set由於要進行比較,性能會比較明顯的降低,因此若是不考慮去重狀況通常不用Set。

四、RandomAccess類接口

I、隨機訪問接口,基於數組實現的如ArrayList和Vector都實現了此接口,而基於鏈表實現Linkedist未實現此接口,所以在進行隨機訪問操做時,鏈表的性能會相差幾個數量級,是因爲LinkedList在進行隨機訪問時須要依據元素所在位置而由前向後或從後向前遍歷集合,而數組則直接經過索引標便可找到。

II、實現RandomAccess接口的集合好比ArrayList,應當使用最普通的for循環而不是foreach循環來遍歷。

這是JDK中推薦給用戶的。JDK的API對於RandomAccess接口的解釋是:實現RandomAccess接口用來代表其支持快速隨機訪問,此接口的主要目的是容許通常的算法更改其行爲,從而將其應用到隨機或連續訪問列表時能提供良好的性能。實際經驗代表,實現RandomAccess接口的類實例,假如是隨機訪問的,使用普通for循環效率將高於使用foreach循環;反過來,若是是順序訪問的,則使用Iterator會效率更高。

具體狀況能夠參考Java語法糖1:可變長度參數以及foreach循環原理

結語

這篇文章也是這些日子對性能調優的一些思考,參雜着《Java系統性能優化實戰》這本書上第二章的內容一塊兒寫了出來。在平常的編碼中,不少地方的代碼都存在着優化的可能,這裏改一點,那裏修一點,不只代碼會變得更漂亮,效率也會更高。

同時須要思惟導圖的話,能夠聯繫我,畢竟知識越分享越香!

相關文章
相關標籤/搜索