關於我們常見的數組應用場景

一、性能考慮,數組是首選

數組在實際的系統開發中應用得越來越少了,我們通常只有在閱讀一些開源項目時纔會看到他們的身影,在Java中它確實沒有List、Set、Map這些集合類的底層也都是通過數組實現的,比如對一個數組集求和這樣的計算:

//數組求和
public static int sum(int[] datas){
    int sum=0;
    for(int i=0; i<datas.length; i++){
        sum+=datas[i];
    }
    return sum;
}

對一個int類型的數組求和,取出所有的數組元素並相加,此算法中如果是基本類型則使用數組效率是最高的,使用集合則效率次之。再看看List求和:

//對列表求和計算
public static int sum(List<Integer> datas){
    int sum=0;
    for(int i=0; i<datas.size(); i++){
        sum +=datas.get(i);//這裏
    }
    return sum;
}

注意標識的字體,這裏其實已經做好了一個拆箱動作,Integer對象通過intValue方法自動轉換成一個int基本類型,對於性能瀕於臨界的系統來說該方案是比較危險的,特別是大數量的時候,首先,在初始化List數組時要進行裝箱動作,把一個int類型包裝成一個Integer對象,雖然有整形池在,但不再整形池範圍內的都會產生一個新的Integer對象,而且衆所周知,基本類型實在棧內存中操作的,而對象則是在堆內存中操作的,棧內存的特點是速度快,容量小,堆內存的特點是速度慢,容量大(從性能上來講,基本類型的處理佔優勢)。其次,在進行求和計算(或者其他遍歷計算)時要做拆箱動作,因此無謂的性能消耗也就產生了。

在實際測試中發現:對基本類型進行求和計算時,數組的效率是集合的10倍。

建議:性能要求較高的場景中使用數組替代集合

二、若有必要,使用變長數組

Java中數組是定長的,一旦經過初始化聲明就不可能改變長度,這在實際使用中非常不方便,比如要對班級學生的信息進行統計,因爲我們不知道一個班級會有多少學生,所有需要有一個足夠大的數組來容納所有的學生,但問題是多大才算足夠大?10年前一臺臺式機64MB的內存已經很牛了,現在要是沒有2GB的內存你都不要意思跟別人交流計算機配置,所有呀,這個足夠大是相對於當時的場景而言的。隨着環境的變化,“足夠大”也可能會轉變成“足夠小”,然後就會出現超出數組最大容量的情況,那該如何解決呢?實際上,可以通過對數組擴容“婉轉”地解決該問題,代碼如下:

public static <T> T[] expandCapacity(T[] datas, int newLen){
    //不能是負值
    newLen = newLen<0?0:newLeng;
    //生成一個新數組,並拷貝原值
    retuen Array.copyOf(datas, newLen);
}

上述代碼中採用的是Array數組工具類的copyOf方法,產生了一個newLen長度新數組,並把原有的值拷貝了進去,之後就可以對超長的元素進行賦值了(依賴類型的不同分別賦值爲0、false或null),使用方法如下:

public static void main(String[] args){
    //班級最多容納60個學生
    Stu[] classes = new Stu[60];
    /*classes初始化...*/
    
    //偶爾一個班級可以容納80人
    classes = expandCapacity(classes, 80);
    /*重新初始化超過限額的20人...*/
}

通過這樣的處理方式,曲折地解決了數組的變長問題。其實,集合的長度自動維護功能的原理與此類似。在實際開發中,如果確實需要變長的數據集,數組也在考慮範圍之內的,不能因固定長度而將其否定之。

三、警惕數組的淺拷貝

有這樣一個例子,第一個箱子裏有赤橙黃綠青藍紫7色氣球,現在希望在第二個箱子中也放入7個氣球,其中最後一個氣球改爲藍色,也就是赤橙黃綠青藍藍7個氣球,那我們很容易就會想到第二個箱子中的氣球可以通過拷貝第一個箱子中的氣球來實現,畢竟有6個氣球是一樣的嘛,來看實現代碼:

public class Client {
    public static void main(String[] args){
        //氣球數量
        int ballonNum = 7;
        //第一個箱子
        Balloon[] box1 = new Balloon[ballonNum];
        //初始化第一個箱子中的氣球
        for(int i=0; i<ballonNum;i++){
            box1[i] = new Balloon(Color.values()[i],i);
        }
        //第二個箱子的氣球是拷貝的第一個箱子裏的
        Balloon[] box2 = Array.copyOf(box1,box1.length);
        //修改最後一個氣球顏色
        box2[6].setColor(Color.Blue);
        //打印出第一個箱子中氣球顏色
        for(Balloon b:box1){
            System.out.println(b);
        }
    }
}
//氣球顏色
enum Color {
    Red, Orange, Yellow, Green, Indigo, Blue, Violet;
}
//氣球
class Balloon{
    //編號
    private int id;
    //顏色
    pricate Color color;
    
    public Balloon(Color _color, int _id){
        color = _color;
        id = _id;
    }
    /*id、color的getter/setter方法省略*/
    //apche-common包下的ToStringBuilder重寫toString方法
    public String toString(){
        return new ToStringBuilder(this).append("編號",id)
                                        .append("顏色",color)
                                        .toString();
    }
}
第二個箱子裏最後一個氣球的顏色毫無疑問是修改成藍色了,不過我們是通過拷貝第一個箱子裏的氣球然後再修改的方式來實現,那會對第一個箱子的氣球顏色有影響嘛?我們看輸出:
Balloon@b2fd8f[編號=0,顏色=Red]
Balloon@a20892[編號=1,顏色=Orange]
Balloon@158b649[編號=2,顏色=Yellow]
Balloon@1037c71[編號=3,顏色=Green]
Balloon@1546e25[編號=4,顏色=Indigo]
balloon@8a0d5d[編號=5,顏色=Blue]
balloon@a470b8[編號=6,顏色=Blue]

最後一個氣球顏色竟然也被修改了,只是希望修改第二個箱子的氣球啊,這是爲何?這是很典型的淺拷貝問題。前面第1章的序列化中也介紹過,但是這裏與之有一點不同:數組中的元素沒有實現Serializable接口。

確實如此,通過copyOf方法產生的數組是一個淺拷貝,這與序列化的淺拷貝完全相同:基本類型是直接拷貝值,其他都是拷貝引用地址。需要說明是,數組的clone方法也是與此相同的,同樣是淺拷貝,並且集合的clone方法也都是淺拷貝,這需要大家再拷貝時多留心了。

問題找到了,解決方案也很簡單,遍歷box1的每個元素,重新生成一個氣球對象,並放置到box2數組中,代碼較檢點,不再累述。

該方法用得最多得地方是在使用集合(如List)進行業務處理時,比如發覺需要拷貝集合中的元素,可集合沒有提供拷貝方法,如果自己寫會很麻煩,所以乾脆使用List.toArray方法轉化成數組,然後通過Arrays.copyOf拷貝,雖然很多時候淺拷貝可以解決業務問題,但更多時候會留下隱患,需要我們提防。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章