java提高篇之数组(2)
前面一節(jié)主要介紹了數(shù)組的基本概念,對(duì)什么是數(shù)組稍微深入了一點(diǎn)點(diǎn),在這篇博文中主要介紹數(shù)組的其他方面。
三、性能?請(qǐng)優(yōu)先考慮數(shù)組
在java中有很多方式來存儲(chǔ)一系列數(shù)據(jù),而且在操作上面比數(shù)組方便的多?但為什么我們還需要使用數(shù)組,而不是替代它呢?數(shù)組與其他種類的容器之間的區(qū)別有三個(gè)方面:效率、類型和保存基本類型的能力。在java中,數(shù)組是一種效率最高的存儲(chǔ)和隨機(jī)訪問對(duì)象引用序列的方式。
在項(xiàng)目設(shè)計(jì)中數(shù)組使用的越來越少了,而且它確實(shí)是沒有List、Set這些集合使用方便,但是在某些方面數(shù)組還是存在一些優(yōu)勢(shì)的,例如:速度,而且集合類的底層也都是通過數(shù)組來實(shí)現(xiàn)的。
| 1 2 3 4 5 6 | --------這是ArrayList的add()------ ????public boolean add(E e) { ????ensureCapacity(size + 1);? // Increments modCount!! ????elementData[size++] = e; ????return true; ????} |
下面利用數(shù)組和list來做一些操作比較。
一、求和
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Long time1 = System.currentTimeMillis(); ????????for(int i = 0 ; i < 100000000 ;i++){ ????????????sum += arrays[i%10]; ????????} ????????Long time2 = System.currentTimeMillis(); ????????System.out.println("數(shù)組求和所花費(fèi)時(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求和所花費(fèi)時(shí)間:" + (time4 - time3) + "毫秒"); --------------Output: 數(shù)組求和所花費(fèi)時(shí)間:696毫秒 List求和所花費(fèi)時(shí)間:3498毫秒 |
從上面的時(shí)間消耗上面來說數(shù)組對(duì)于基本類型的求和計(jì)算的速度是集合的5倍左右。其實(shí)在list集合中,求和當(dāng)中有一個(gè)致命的動(dòng)作:list.get(i)。這個(gè)動(dòng)作是進(jìn)行拆箱動(dòng)作,Integer對(duì)象通過intValue方法自動(dòng)轉(zhuǎn)換成一個(gè)int基本類型,在這里就產(chǎn)生了不必要的性能消耗。
? 所以在性能要求較高的場(chǎng)景中請(qǐng)優(yōu)先考慮數(shù)組。
四、變長(zhǎng)數(shù)組?
數(shù)組是定長(zhǎng)的,一旦初始化聲明后是不可改變長(zhǎng)度的。這對(duì)我們?cè)趯?shí)際開發(fā)中是非常不方便的,聰明的我們肯定是可以找到方法來實(shí)現(xiàn)的。就如java不能實(shí)現(xiàn)多重繼承一樣,我們一樣可以利用內(nèi)部類和接口來實(shí)現(xiàn)(請(qǐng)參考:java提高篇(九)—–實(shí)現(xiàn)多重繼承)。
那么如何來實(shí)現(xiàn)變長(zhǎng)數(shù)組呢?我們可以利用List集合add方法里面的擴(kuò)容思路來模擬實(shí)現(xiàn)。下面是ArrayList的擴(kuò)容方法:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public void ensureCapacity(int minCapacity) { ????????modCount++;? ????????int oldCapacity = elementData.length; ????????/** ?????????* 若當(dāng)前需要的長(zhǎng)度超過數(shù)組長(zhǎng)度時(shí)進(jìn)行擴(kuò)容處理 ?????????*/ ????????if (minCapacity > oldCapacity) { ????????????Object oldData[] = elementData;??? ????????????int newCapacity = (oldCapacity * 3) / 2 + 1;??? //擴(kuò)容 ????????????if (newCapacity < minCapacity) ????????????????newCapacity = minCapacity; ????????????//拷貝數(shù)組,生成新的數(shù)組 ????????????elementData = Arrays.copyOf(elementData, newCapacity); ????????} ????} |
這段代碼對(duì)我們有用的地方就在于if語句后面。它的思路是將原始數(shù)組拷貝到新數(shù)組中,新數(shù)組是原始數(shù)組長(zhǎng)度的1.5倍。所以模擬的數(shù)組擴(kuò)容代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | public class ArrayUtils { ????/** ?????* @desc 對(duì)數(shù)組進(jìn)行擴(kuò)容 ?????* @author chenssy ?????* @data 2013-12-8 ?????* @param <T> ?????* @param datas 原始數(shù)組 ?????* @param newLen 擴(kuò)容大小 ?????* @return T[] ?????*/ ????public static <T> T[] expandCapacity(T[] datas,int newLen){ ????????newLen = newLen < 0 ? datas.length :datas.length + newLen;?? ????????//生成一個(gè)新的數(shù)組 ????????return Arrays.copyOf(datas, newLen); ????} ????/** ?????* @desc 對(duì)數(shù)組進(jìn)行擴(kuò)容處理,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;????? //擴(kuò)容原始數(shù)組的1.5倍 ????????//生成一個(gè)新的數(shù)組 ????????return Arrays.copyOf(datas, newLen); ????} ????/** ?????* @desc 對(duì)數(shù)組進(jìn)行擴(kuò)容處理, ?????* @author chenssy ?????* @data 2013-12-8 ?????* @param <T> ?????* @param datas 原始數(shù)組 ?????* @param mulitiple 擴(kuò)容的倍數(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 ); ????} } |
通過這種迂回的方式我們可以實(shí)現(xiàn)數(shù)組的擴(kuò)容。因此在項(xiàng)目中如果確實(shí)需要變長(zhǎng)的數(shù)據(jù)集,數(shù)組也是在考慮范圍之內(nèi)的,我們不能因?yàn)樗枪潭ㄩL(zhǎng)度而排斥他!
五、數(shù)組復(fù)制問題
以前在做集合拷貝的時(shí)候由于集合沒有拷貝的方法,所以一個(gè)一個(gè)的復(fù)制是非常麻煩的,所以我就干脆使用List.toArray()方法轉(zhuǎn)換成數(shù)組然后再通過Arrays.copyOf拷貝,在轉(zhuǎn)換成集合,個(gè)人覺得非常方便,殊不知我已經(jīng)陷入了其中的陷進(jìn)!我們知道若數(shù)組元素為對(duì)象,則數(shù)組里面數(shù)據(jù)是對(duì)象引用
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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ù)組是一個(gè)淺拷貝。同時(shí)數(shù)組的clone()方法也是,集合的clone()方法也是,所以我們?cè)谑褂每截惙椒ǖ耐瑫r(shí)一定要注意淺拷貝這問題。
有關(guān)于深淺拷貝的博文,參考:
漸析java的淺拷貝和深拷貝:http://www.cnblogs.com/chenssy/p/3308489.html。
使用序列化實(shí)現(xiàn)對(duì)象的拷貝:http://www.cnblogs.com/chenssy/p/3382979.html。
六、數(shù)組轉(zhuǎn)換為L(zhǎng)ist注意地方
我們經(jīng)常需要使用到Arrays這個(gè)工具的asList()方法將其轉(zhuǎn)換成列表。方便是方便,但是有時(shí)候會(huì)出現(xiàn)莫名其妙的問題。如下:
| 1 2 3 4 5 6 7 | 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,是的你沒有看錯(cuò), 結(jié)果就是1。但是為什么會(huì)是1而不是5呢?先看asList()的源碼
| 1 2 3 | public static <T> List<T> asList(T... a) { ????????return new ArrayList<T>(a); ????} |
注意這個(gè)參數(shù):T…a,這個(gè)參數(shù)是一個(gè)泛型的變長(zhǎng)參數(shù),我們知道基本數(shù)據(jù)類型是不可能泛型化的,也是就說8個(gè)基本數(shù)據(jù)類型是不可作為泛型參數(shù)的,但是為什么編譯器沒有報(bào)錯(cuò)呢?這是因?yàn)樵趈ava中,數(shù)組會(huì)當(dāng)做一個(gè)對(duì)象來處理,它是可以泛型的,所以我們的程序是把一個(gè)int型的數(shù)組作為了T的類型,所以在轉(zhuǎn)換之后List中就只會(huì)存在一個(gè)類型為int數(shù)組的元素了。所以我們這樣的程序System.out.println(datas.equals(list.get(0)));輸出結(jié)果肯定是true。當(dāng)然如果將int改為Integer,則長(zhǎng)度就會(huì)變成5了。
我們?cè)诳聪旅娉绦?#xff1a;
| 1 2 3 4 5 6 | 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); ????} |
這個(gè)程序非常簡(jiǎn)單,就是講一個(gè)數(shù)組轉(zhuǎn)換成list,然后改變集合中值,但是運(yùn)行呢?
| 1 2 3 4 | 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) |
編譯沒錯(cuò),但是運(yùn)行竟然出現(xiàn)了異常錯(cuò)誤!UnsupportedOperationException ,當(dāng)不支持請(qǐng)求的操作時(shí),就會(huì)拋出該異常。從某種程度上來說就是不支持add方法,我們知道這是不可能的!什么原因引起這個(gè)異常呢?先看asList()的源代碼:
| 1 2 3 | public static <T> List<T> asList(T... a) { ????????return new ArrayList<T>(a); ????} |
這里是直接返回一個(gè)ArrayList對(duì)象返回,但是注意這個(gè)ArrayList并不是java.util.ArrayList,而是Arrays工具類的一個(gè)內(nèi)之類:
| 1 2 3 4 5 6 7 8 9 10 11 | 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; ????} ???????/** 省略方法 **/ ????} |
但是這個(gè)內(nèi)部類并沒有提供add()方法,那么查看父類:
| 1 2 3 4 5 6 7 | public boolean add(E e) { ????add(size(), e); ????return true; ????} ????public void add(int index, E element) { ????throw new UnsupportedOperationException(); ????} |
這里父類僅僅只是提供了方法,方法的具體實(shí)現(xiàn)卻沒有,所以具體的實(shí)現(xiàn)需要子類自己來提供,但是非常遺憾
這個(gè)內(nèi)部類ArrayList并沒有提高add的實(shí)現(xiàn)方法。在ArrayList中,它主要提供了如下幾個(gè)方法:
1、size:元素?cái)?shù)量
2、toArray:轉(zhuǎn)換為數(shù)組,實(shí)現(xiàn)了數(shù)組的淺拷貝。
3、get:獲得指定元素。
4、contains:是否包含某元素。
所以綜上所述,asList返回的是一個(gè)長(zhǎng)度不可變的列表。數(shù)組是多長(zhǎng),轉(zhuǎn)換成的列表是多長(zhǎng),我們是無法通過add、remove來增加或者減少其長(zhǎng)度的。
參考文獻(xiàn):《編寫高質(zhì)量代碼–改善Java程序的151個(gè)建議》
本系列:
- java提高篇之?dāng)?shù)組(1):認(rèn)識(shí)JAVA數(shù)組
- java提高篇之?dāng)?shù)組(2)
總結(jié)
以上是生活随笔為你收集整理的java提高篇之数组(2)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java提高篇之数组(1):认识JAVA
- 下一篇: Java 多维数组遍历