鍍金池/ 教程/ Java/ 數(shù)組之二
Java 集合細節(jié)(四):保持 compareTo 和 equals 同步
Iterator
使用序列化實現(xiàn)對象的拷貝
fail-fast 機制
關(guān)鍵字 final
Vector
HashTable
Java 集合細節(jié)(一):請為集合指定初始容量
強制類型轉(zhuǎn)換
數(shù)組之一:認識 JAVA 數(shù)組
Java 集合細節(jié)(三):subList 的缺陷
hashCode
ArrayList
數(shù)組之二
List 總結(jié)
LinkedList
Java 提高篇(九)—–實現(xiàn)多重繼承
Java 的四舍五入
關(guān)鍵字 static
理解 Java 的三大特性之多態(tài)
抽象類與接口
集合大家族
異常(二)
Java 集合細節(jié)(二):asList 的缺陷
Map 總結(jié)
TreeSet
equals() 方法總結(jié)
Java 提高篇(十)—–詳解匿名內(nèi)部類
HashMap
Stack
詳解內(nèi)部類
TreeMap
異常(一)
詳解 Java 定時任務
HashSet
字符串
理解 Java 的三大特性之繼承
理解 Java 的三大特性之封裝
代碼塊

數(shù)組之二

前面一節(jié)主要介紹了數(shù)組的基本概念,對什么是數(shù)組稍微深入了一點點,在這篇博文中主要介紹數(shù)組的其他方面。

三、性能?請優(yōu)先考慮數(shù)組

在 Java 中有很多方式來存儲一系列數(shù)據(jù),而且在操作上面比數(shù)組方便的多?但為什么我們還需要使用數(shù)組,而不是替代它呢?數(shù)組與其他種類的容器之間的區(qū)別有三個方面:效率、類型和保存基本類型的能力。在 Java 中,數(shù)組是一種效率最高的存儲和隨機訪問對象引用序列的方式。

在項目設計中數(shù)組使用的越來越少了,而且它確實是沒有 List、Set 這些集合使用方便,但是在某些方面數(shù)組還是存在一些優(yōu)勢的,例如:速度,而且集合類的底層也都是通過數(shù)組來實現(xiàn)的。


    --------這是ArrayList的add()------
        public boolean add(E e) {
        ensureCapacity(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
        }

下面利用數(shù)組和 list 來做一些操作比較。

一、求和


    Long time1 = System.currentTimeMillis();
            for(int i = 0 ; i < 100000000 ;i++){
                sum += arrays[i%10];
            }
            Long time2 = System.currentTimeMillis();
            System.out.println("數(shù)組求和所花費時間:" + (time2 - time1) + "毫秒");
            Long time3 = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                sum  += list.get(i%10);
            }
            Long time4 = System.currentTimeMillis();
            System.out.println("List求和所花費時間:" + (time4 - time3) + "毫秒");
    --------------Output:
    數(shù)組求和所花費時間:696毫秒
    List求和所花費時間:3498毫秒

從上面的時間消耗上面來說數(shù)組對于基本類型的求和計算的速度是集合的 5 倍左右。其實在 list 集合中,求和當中有一個致命的動作:list.get(i)。這個動作是進行拆箱動作,Integer 對象通過 intValue 方法自動轉(zhuǎn)換成一個 int 基本類型,在這里就產(chǎn)生了不必要的性能消耗。

所以在性能要求較高的場景中請優(yōu)先考慮數(shù)組。

四、變長數(shù)組?

數(shù)組是定長的,一旦初始化聲明后是不可改變長度的。這對我們在實際開發(fā)中是非常不方便的,聰明的我們肯定是可以找到方法來實現(xiàn)的。就如 Java 不能實現(xiàn)多重繼承一樣,我們一樣可以利用內(nèi)部類和接口來實現(xiàn)(請參考:Java提高篇(九)—–實現(xiàn)多重繼承 )。

那么如何來實現(xiàn)變長數(shù)組呢?我們可以利用 List 集合 add 方法里面的擴容思路來模擬實現(xiàn)。下面是 ArrayList 的擴容方法:


    public void ensureCapacity(int minCapacity) {
            modCount++;  
            int oldCapacity = elementData.length;
            /**
             * 若當前需要的長度超過數(shù)組長度時進行擴容處理
             */
            if (minCapacity > oldCapacity) {
                Object oldData[] = elementData;    
                int newCapacity = (oldCapacity * 3) / 2 + 1;    //擴容
                if (newCapacity < minCapacity)
                    newCapacity = minCapacity;
                //拷貝數(shù)組,生成新的數(shù)組
                elementData = Arrays.copyOf(elementData, newCapacity);
            }
        }

這段代碼對我們有用的地方就在于 if 語句后面。它的思路是將原始數(shù)組拷貝到新數(shù)組中,新數(shù)組是原始數(shù)組長度的 1.5 倍。所以模擬的數(shù)組擴容代碼如下:


    public class ArrayUtils {
        /**
         * @desc 對數(shù)組進行擴容
         * @author chenssy
         * @data 2013-12-8
         * @param <T>
         * @param datas 原始數(shù)組
         * @param newLen 擴容大小
         * @return T[]
         */
         public static <T> T[] expandCapacity(T[] datas,int newLen){
            newLen = newLen < 0 ? datas.length :datas.length + newLen;   
            //生成一個新的數(shù)組
            return Arrays.copyOf(datas, newLen);
         }

        /**
         * @desc 對數(shù)組進行擴容處理,1.5倍
         * @author chenssy
         * @data 2013-12-8
         * @param <T>
         * @param datas  原始數(shù)組
         * @return T[]
         */
        public static <T> T[] expandCapacity(T[] datas){
            int newLen = (datas.length * 3) / 2;      //擴容原始數(shù)組的1.5倍
            //生成一個新的數(shù)組
            return Arrays.copyOf(datas, newLen);
        }

        /**
         * @desc 對數(shù)組進行擴容處理,
         * @author chenssy
         * @data 2013-12-8
         * @param <T>
         * @param datas 原始數(shù)組
         * @param mulitiple 擴容的倍數(shù)
         * @return T[]
         */
        public static <T> T[] expandCapacityMul(T[] datas,int mulitiple){
            mulitiple = mulitiple < 0 ? 1 : mulitiple;
            int newLen = datas.length * mulitiple;
            return Arrays.copyOf(datas,newLen );
        }
    }

通過這種迂回的方式我們可以實現(xiàn)數(shù)組的擴容。因此在項目中如果確實需要變長的數(shù)據(jù)集,數(shù)組也是在考慮范圍之內(nèi)的,我們不能因為他是固定長度而排斥他!

五、數(shù)組復制問題

以前在做集合拷貝的時候由于集合沒有拷貝的方法,所以一個一個的復制是非常麻煩的,所以我就干脆使用 List.toArray() 方法轉(zhuǎn)換成數(shù)組然后再通過 Arrays.copyOf 拷貝,在轉(zhuǎn)換成集合,個人覺得非常方便,殊不知我已經(jīng)陷入了其中的陷進!我們知道若數(shù)組元素為對象,則數(shù)組里面數(shù)據(jù)是對象引用


    public class Test {
        public static void main(String[] args) {
            Person person_01 = new Person("chenssy_01");

            Person[] persons1 = new Person[]{person_01};
            Person[] persons2 = Arrays.copyOf(persons1,persons1.length);

            System.out.println("數(shù)組persons1:");
            display(persons1);
            System.out.println("---------------------");
            System.out.println("數(shù)組persons2:");
            display(persons2);
            //改變其值
            persons2[0].setName("chessy_02");
            System.out.println("------------改變其值后------------");
            System.out.println("數(shù)組persons1:");
            display(persons1);
            System.out.println("---------------------");
            System.out.println("數(shù)組persons2:");
            display(persons2);
        }
        public static void display(Person[] persons){
            for(Person person : persons){
                System.out.println(person.toString());
            }
        }
    }
    -------------Output:
    數(shù)組persons1:
    姓名是:chenssy_01
    ---------------------
    數(shù)組persons2:
    姓名是:chenssy_01
    ------------改變其值后------------
    數(shù)組persons1:
    姓名是:chessy_02
    ---------------------
    數(shù)組persons2:
    姓名是:chessy_02

從結(jié)果中發(fā)現(xiàn),persons1 中的值也發(fā)生了改變,這是典型的淺拷貝問題。所以通過 Arrays.copyOf() 方法產(chǎn)生的數(shù)組是一個淺拷貝。同時數(shù)組的 clone() 方法也是,集合的 clone() 方法也是,所以我們在使用拷貝方法的同時一定要注意淺拷貝這問題。

有關(guān)于深淺拷貝的博文,參考:

漸析Java的淺拷貝和深拷貝:http://www.cnblogs.com/chenssy/p/3308489.html

使用序列化實現(xiàn)對象的拷貝:http://www.cnblogs.com/chenssy/p/3382979.html 。

六、數(shù)組轉(zhuǎn)換為 List 注意地方

我們經(jīng)常需要使用到 Arrays 這個工具的 asList() 方法將其轉(zhuǎn)換成列表。方便是方便,但是有時候會出現(xiàn)莫名其妙的問題。如下:


    public static void main(String[] args) {
            int[] datas = new int[]{1,2,3,4,5};
            List list = Arrays.asList(datas);
            System.out.println(list.size());
        }
    ------------Output:
    1

結(jié)果是 1,是的你沒有看錯, 結(jié)果就是 1。但是為什么會是 1 而不是 5 呢?先看 asList() 的源碼


    public static <T> List<T> asList(T... a) {
            return new ArrayList<T>(a);
        }

注意這個參數(shù):T…a,這個參數(shù)是一個泛型的變長參數(shù),我們知道基本數(shù)據(jù)類型是不可能泛型化的,也是就說 8 個基本數(shù)據(jù)類型是不可作為泛型參數(shù)的,但是為什么編譯器沒有報錯呢?這是因為在 Java 中,數(shù)組會當做一個對象來處理,它是可以泛型的,所以我們的程序是把一個 int 型的數(shù)組作為了 T 的類型,所以在轉(zhuǎn)換之后 List 中就只會存在一個類型為 int 數(shù)組的元素了。所以我們這樣的程序 System.out.println(datas.equals(list.get(0)));輸出結(jié)果肯定是 true。當然如果將int改為 Integer,則長度就會變成 5 了。

我們在看下面程序:


    enum Week{Sum,Mon,Tue,Web,Thu,Fri,Sat}
        public static void main(String[] args) {
            Week[] weeks = {Week.Sum,Week.Mon,Week.Tue,Week.Web,Week.Thu,Week.Fri};
            List<Week> list = Arrays.asList(weeks);
            list.add(Week.Sat);
        }

這個程序非常簡單,就是講一個數(shù)組轉(zhuǎn)換成 list,然后改變集合中值,但是運行呢?


    Exception in thread "main"   java.lang.UnsupportedOperationException
        at java.util.AbstractList.add(AbstractList.java:131)
        at java.util.AbstractList.add(AbstractList.java:91)
        at com.array.Test.main(Test.java:18)

編譯沒錯,但是運行竟然出現(xiàn)了異常錯誤!UnsupportedOperationException,當不支持請求的操作時,就會拋出該異常。從某種程度上來說就是不支持add方法,我們知道這是不可能的!什么原因引起這個異常呢?先看 asList() 的源代碼:


    public static <T> List<T> asList(T... a) {
            return new ArrayList<T>(a);
        }

這里是直接返回一個 ArrayList 對象返回,但是注意這個 ArrayList 并不是 java.util.ArrayList,而是 Arrays 工具類的一個內(nèi)之類:


    private static class ArrayList<E> extends AbstractList<E>
         implements RandomAccess, java.io.Serializable{
            private static final long serialVersionUID =   -2764017481108945198L;
            private final E[] a;
            ArrayList(E[] array) {
                if (array==null)
                    throw new NullPointerException();
            a = array;
        }
           /** 省略方法 **/
        }

但是這個內(nèi)部類并沒有提供 add() 方法,那么查看父類:


    public boolean add(E e) {
        add(size(), e);
        return true;
        }
        public void add(int index, E element) {
        throw new UnsupportedOperationException();
        }

這里父類僅僅只是提供了方法,方法的具體實現(xiàn)卻沒有,所以具體的實現(xiàn)需要子類自己來提供,但是非常遺憾

這個內(nèi)部類 ArrayList 并沒有提高 add 的實現(xiàn)方法。在 ArrayList 中,它主要提供了如下幾個方法:

1、size:元素數(shù)量

2、toArray:轉(zhuǎn)換為數(shù)組,實現(xiàn)了數(shù)組的淺拷貝。

3、get:獲得指定元素。

4、contains:是否包含某元素。

所以綜上所述,asList 返回的是一個長度不可變的列表。數(shù)組是多長,轉(zhuǎn)換成的列表是多長,我們是無法通過 add、remove 來增加或者減少其長度的。

參考文獻:《編寫高質(zhì)量代碼–改善 Java 程序的 151 個建議》