前面一節主要介紹了數組的基本概念,對什么是數組稍微深入了一點點,在這篇博文中主要介紹數組的其他方面。
三、性能?請優先考慮數組
在java中有很多方式來存儲一系列數據,而且在操作上面比數組方便的多?但為什么我們還需要使用數組,而不是替代它呢?數組與其他種類的容器之間的區別有三個方面:效率、類型和保存基本類型的能力。在java中,數組是一種效率最高的存儲和隨機訪問對象引用序列的方式。
在項目設計中數組使用的越來越少了,而且它確實是沒有List、Set這些集合使用方便,但是在某些方面數組還是存在一些優勢的,例如:速度,而且集合類的底層也都是通過數組來實現的。
--------這是ArrayList的add()------ public boolean add(E e) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
下面利用數組和list來做一些操作比較。
一、求和
Long time1 = System.currentTimeMillis(); for(int i = 0 ; i < 100000000 ;i++){ sum += arrays[i%10]; } Long time2 = System.currentTimeMillis(); System.out.println("數組求和所花費時間:" + (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: 數組求和所花費時間:696毫秒 List求和所花費時間:3498毫秒
從上面的時間消耗上面來說數組對於基本類型的求和計算的速度是集合的5倍左右。其實在list集合中,求和當中有一個致命的動作:list.get(i)。這個動作是進行拆箱動作,Integer對象通過intValue方法自動轉換成一個int基本類型,在這里就產生了不必要的性能消耗。
所以在性能要求較高的場景中請優先考慮數組。
四、變長數組?
數組是定長的,一旦初始化聲明后是不可改變長度的。這對我們在實際開發中是非常不方便的,聰明的我們肯定是可以找到方法來實現的。就如java不能實現多重繼承一樣,我們一樣可以利用內部類和接口來實現(請參考:java提高篇(九)-----實現多重繼承)。
那么如何來實現變長數組呢?我們可以利用List集合add方法里面的擴容思路來模擬實現。下面是ArrayList的擴容方法:
public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; /** * 若當前需要的長度超過數組長度時進行擴容處理 */ if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3) / 2 + 1; //擴容 if (newCapacity < minCapacity) newCapacity = minCapacity; //拷貝數組,生成新的數組 elementData = Arrays.copyOf(elementData, newCapacity); } }
這段代碼對我們有用的地方就在於if語句后面。它的思路是將原始數組拷貝到新數組中,新數組是原始數組長度的1.5倍。所以模擬的數組擴容代碼如下:
public class ArrayUtils { /** * @desc 對數組進行擴容 * @author chenssy * @data 2013-12-8 * @param <T> * @param datas 原始數組 * @param newLen 擴容大小 * @return T[] */ public static <T> T[] expandCapacity(T[] datas,int newLen){ newLen = newLen < 0 ? datas.length :datas.length + newLen; //生成一個新的數組 return Arrays.copyOf(datas, newLen); } /** * @desc 對數組進行擴容處理,1.5倍 * @author chenssy * @data 2013-12-8 * @param <T> * @param datas 原始數組 * @return T[] */ public static <T> T[] expandCapacity(T[] datas){ int newLen = (datas.length * 3) / 2; //擴容原始數組的1.5倍 //生成一個新的數組 return Arrays.copyOf(datas, newLen); } /** * @desc 對數組進行擴容處理, * @author chenssy * @data 2013-12-8 * @param <T> * @param datas 原始數組 * @param mulitiple 擴容的倍數 * @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 ); } }
通過這種迂回的方式我們可以實現數組的擴容。因此在項目中如果確實需要變長的數據集,數組也是在考慮范圍之內的,我們不能因為他是固定長度而排斥他!
五、數組復制問題
以前在做集合拷貝的時候由於集合沒有拷貝的方法,所以一個一個的復制是非常麻煩的,所以我就干脆使用List.toArray()方法轉換成數組然后再通過Arrays.copyOf拷貝,在轉換成集合,個人覺得非常方便,殊不知我已經陷入了其中的陷進!我們知道若數組元素為對象,則數組里面數據是對象引用
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("數組persons1:"); display(persons1); System.out.println("---------------------"); System.out.println("數組persons2:"); display(persons2); //改變其值 persons2[0].setName("chessy_02"); System.out.println("------------改變其值后------------"); System.out.println("數組persons1:"); display(persons1); System.out.println("---------------------"); System.out.println("數組persons2:"); display(persons2); } public static void display(Person[] persons){ for(Person person : persons){ System.out.println(person.toString()); } } } -------------Output: 數組persons1: 姓名是:chenssy_01 --------------------- 數組persons2: 姓名是:chenssy_01 ------------改變其值后------------ 數組persons1: 姓名是:chessy_02 --------------------- 數組persons2: 姓名是:chessy_02
從結果中發現,persons1中的值也發生了改變,這是典型的淺拷貝問題。所以通過Arrays.copyOf()方法產生的數組是一個淺拷貝。同時數組的clone()方法也是,集合的clone()方法也是,所以我們在使用拷貝方法的同時一定要注意淺拷貝這問題。
有關於深淺拷貝的博文,參考:
漸析java的淺拷貝和深拷貝:http://www.cnblogs.com/chenssy/p/3308489.html。
使用序列化實現對象的拷貝:http://www.cnblogs.com/chenssy/p/3382979.html。
六、數組轉換為List注意地方
我們經常需要使用到Arrays這個工具的asList()方法將其轉換成列表。方便是方便,但是有時候會出現莫名其妙的問題。如下:
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
結果是1,是的你沒有看錯, 結果就是1。但是為什么會是1而不是5呢?先看asList()的源碼
public static <T> List<T> asList(T... a) { return new ArrayList<T>(a); }
注意這個參數:T…a,這個參數是一個泛型的變長參數,我們知道基本數據類型是不可能泛型化的,也是就說8個基本數據類型是不可作為泛型參數的,但是為什么編譯器沒有報錯呢?這是因為在java中,數組會當做一個對象來處理,它是可以泛型的,所以我們的程序是把一個int型的數組作為了T的類型,所以在轉換之后List中就只會存在一個類型為int數組的元素了。所以我們這樣的程序System.out.println(datas.equals(list.get(0)));輸出結果肯定是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); }
這個程序非常簡單,就是講一個數組轉換成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)
編譯沒錯,但是運行竟然出現了異常錯誤!UnsupportedOperationException ,當不支持請求的操作時,就會拋出該異常。從某種程度上來說就是不支持add方法,我們知道這是不可能的!什么原因引起這個異常呢?先看asList()的源代碼:
public static <T> List<T> asList(T... a) { return new ArrayList<T>(a); }
這里是直接返回一個ArrayList對象返回,但是注意這個ArrayList並不是java.util.ArrayList,而是Arrays工具類的一個內之類:
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; } /** 省略方法 **/ }
但是這個內部類並沒有提供add()方法,那么查看父類:
public boolean add(E e) { add(size(), e); return true; } public void add(int index, E element) { throw new UnsupportedOperationException(); }
這里父類僅僅只是提供了方法,方法的具體實現卻沒有,所以具體的實現需要子類自己來提供,但是非常遺憾
這個內部類ArrayList並沒有提高add的實現方法。在ArrayList中,它主要提供了如下幾個方法:
1、size:元素數量
2、toArray:轉換為數組,實現了數組的淺拷貝。
3、get:獲得指定元素。
4、contains:是否包含某元素。
所以綜上所述,asList返回的是一個長度不可變的列表。數組是多長,轉換成的列表是多長,我們是無法通過add、remove來增加或者減少其長度的。
參考文獻:《編寫高質量代碼--改善Java程序的151個建議》