Java集合篇:集合细节:为集合指定初始容量、asList的缺陷、subList的缺陷
一、為集合指定初始容量:
集合是我們在Java編程中使用非常廣泛的,它就像大海,海納百川,像萬能容器,盛裝萬物,而且這個大海,萬能容器還可以無限變大(如果條件允許)。當這個海、容器的量變得非常大的時候,它的初始容量就會顯得很重要了,因為挖海、擴容是需要消耗大量的人力物力財力的。同樣的道理,Collection的初始容量也顯得異常重要。所以:對于已知的情景,請為集合指定初始容量。
public static void main(String[] args) {StudentVO student = null;List<StudentVO> list1 = new ArrayList<>();long begin1 = System.currentTimeMillis();for(int i = 0 ; i < 1000000; i++){student = new StudentVO(i,"chenssy_"+i,i);list1.add(student);}long end1 = System.currentTimeMillis();System.out.println("list1 time:" + (end1 - begin1));List<StudentVO> list2 = new ArrayList<>(1000000);long begin2 = System.currentTimeMillis();for(int i = 0 ; i < 1000000; i++){student = new StudentVO(i,"chenssy_"+i,i);list2.add(student);}long end2 = System.currentTimeMillis();System.out.println("list2 time:" + (end2 - begin2));}上面代碼兩個list都是插入1000000條數據,只不過list1沒有沒有申請初始化容量,而list2初始化容量1000000。那運行結果如下:
list1 time:1638 list2 time:921從上面的運行結果我們可以看出list2的速度是list1的兩倍左右。在前面LZ就提過,ArrayList的擴容機制是比較消耗資源的。我們先看ArrayList的add方法:
public boolean add(E e) { ensureCapacity(size + 1); elementData[size++] = e; return true; } public void ensureCapacity(int minCapacity) { modCount++; //修改計數器int oldCapacity = elementData.length; //當前需要的長度超過了數組長度,進行擴容處理if (minCapacity > oldCapacity) { Object oldData[] = elementData; //新的容量 = 舊容量 * 1.5 + 1int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; //數組拷貝,生成新的數組 elementData = Arrays.copyOf(elementData, newCapacity); } }ArrayList每次新增一個元素,就會檢測ArrayList的當前容量是否已經達到臨界點,如果達到臨界點則會擴容1.5倍。然而ArrayList 的擴容以及數組的拷貝生成一個新的數組是想當耗資源的。所有若我們事先已經知道集合的使用場合,知道集合的大概范圍,我們最好是指定初始容量,這樣對資源的利用會更好,尤其是大數據量的前提下,效率的提升和資源的利用會顯得更加具有優勢。
java集合細節1.1:請為集合指定初始容量。
?
二、asList的缺陷:
1、避免使用基本數據類型數組轉換為列表:
public static void main(String[] args) {int[] ints = {1,2,3,4,5};List list = Arrays.asList(ints);System.out.println("list'size:" + list.size());}------------------------------------outPut:list'size:1程序的運行結果并沒有像我們預期的那樣是5而是逆天的1,這是什么情況?先看源碼:
public static <T> List<T> asList(T... a) {return new ArrayList<>(a);}asList 接收的參數是一個泛型的變長數組,我們知道基本數據類型是無法泛型化的,也就是說8個基本類型是無法作為asList的參數的, 要想作為泛型參數就必須使用其所對應的包裝類型。但是這個這個實例中為什么沒有出錯呢?因為該實例是將int 類型的數組當做其參數,而在Java中數組是一個對象,它是可以泛型化的。所以該例子是不會產生錯誤的。既然例子是將整個int 類型的數組當做泛型參數,那么經過asList轉換就只有一個int 的列表了。如下:
public static void main(String[] args) {int[] ints = {1,2,3,4,5};List list = Arrays.asList(ints);System.out.println("list 的類型:" + list.get(0).getClass());System.out.println("list.get(0) == ints:" + list.get(0).equals(ints)); } -------------------------------------------- outPut: list 的類型:class [I list.get(0) == ints:true從這個運行結果我們可以充分證明list里面的元素就是int數組。弄清楚這點了,那么修改方法也就一目了然了:將int 改變為Integer。
public static void main(String[] args) {Integer[] ints = {1,2,3,4,5};List list = Arrays.asList(ints);System.out.println("list'size:" + list.size());System.out.println("list.get(0) 的類型:" + list.get(0).getClass());System.out.println("list.get(0) == ints[0]:" + list.get(0).equals(ints[0]));}----------------------------------------outPut:list'size:5list.get(0) 的類型:class java.lang.Integerlist.get(0) == ints[0]:trueJava集合細節2.1:在使用asList時不要將基本數據類型當做參數。
2、asList產生的列表不可操作:
對于上面的實例我們再做一個小小的修改:
public static void main(String[] args) {Integer[] ints = {1,2,3,4,5};List list = Arrays.asList(ints);list.add(6);}該實例就是講ints通過asList轉換為list 類別,然后再通過add方法加一個元素,這個實例簡單的不能再簡單了,但是運行結果呢?打出我們所料:
Exception in thread "main" java.lang.UnsupportedOperationExceptionat java.util.AbstractList.add(Unknown Source)at java.util.AbstractList.add(Unknown Source)at com.chenssy.test.arrayList.AsListTest.main(AsListTest.java:10)運行結果竟然拋出UnsupportedOperationException異常,該異常表示list不支持add方法。這就讓我們郁悶了,list怎么可能不支持add方法呢?難道jdk腦袋堵塞了?我們再看asList的源碼:
public static <T> List<T> asList(T... a) {return new ArrayList<>(a);}?asList接受參數后,直接new 一個ArrayList,到這里看應該是沒有錯誤的啊?別急,再往下看:
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;}//.................}?這是ArrayList的源碼,從這里我們可以看出,此ArrayList不是java.util.ArrayList,他是Arrays的內部類。該內部類提供了size、toArray、get、set、indexOf、contains方法,而像add、remove等改變list結果的方法從AbstractList父類繼承過來,同時這些方法也比較奇葩,它直接拋出UnsupportedOperationException異常:
public boolean add(E e) {add(size(), e);return true;}public E set(int index, E element) {throw new UnsupportedOperationException();}public void add(int index, E element) {throw new UnsupportedOperationException();}public E remove(int index) {throw new UnsupportedOperationException();}通過這些代碼可以看出asList返回的列表只不過是一個披著list的外衣,它并沒有list的基本特性(變長)。該list是一個長度不可變的列表,傳入參數的數組有多長,其返回的列表就只能是多長。所以:
Java集合細節2.2:不要試圖改變asList返回的列表。
?
三、subList的缺陷:
我們經常使用subString方法來對String對象進行分割處理,同時我們也可以使用subList、subMap、subSet來對List、Map、Set進行分割處理,但是這個分割存在某些瑕疵。
1、subList返回的僅僅只是一個視圖:
首先我們先看如下實例:
public static void main(String[] args) {List<Integer> list1 = new ArrayList<Integer>();list1.add(1);list1.add(2);//通過構造函數新建一個包含list1的列表 list2List<Integer> list2 = new ArrayList<Integer>(list1);//通過subList生成一個與list1一樣的列表 list3List<Integer> list3 = list1.subList(0, list1.size());//修改list3list3.add(3);System.out.println("list1 == list2:" + list1.equals(list2));System.out.println("list1 == list3:" + list1.equals(list3));}?這個例子非常簡單,無非就是通過構造函數、subList重新生成一個與list1一樣的list,然后修改list3,最后比較list1 == list2?、list1 == list3?。按照我們常規的思路應該是這樣的:因為list3通過add新增了一個元素,那么它肯定與list1不等,而list2是通過list1構造出來的,所以應該相等,所以結果應該是:
list1 == list2:true list1 == list3: false首先我們先不論結果的正確與否,我們先看subList的源碼:
public List<E> subList(int fromIndex, int toIndex) {subListRangeCheck(fromIndex, toIndex, size);return new SubList(this, 0, fromIndex, toIndex);}subListRangeCheck方式是判斷fromIndex、toIndex是否合法,如果合法就直接返回一個subList對象,注意在產生該new該對象的時候傳遞了一個參數 this ,該參數非常重要,因為他代表著原始list。
/*** 繼承AbstractList類,實現RandomAccess接口*/private class SubList extends AbstractList<E> implements RandomAccess {private final AbstractList<E> parent; //列表private final int parentOffset; private final int offset;int size;//構造函數SubList(AbstractList<E> parent,int offset, int fromIndex, int toIndex) {this.parent = parent;this.parentOffset = fromIndex;this.offset = offset + fromIndex;this.size = toIndex - fromIndex;this.modCount = ArrayList.this.modCount;}//set方法public E set(int index, E e) {rangeCheck(index);checkForComodification();E oldValue = ArrayList.this.elementData(offset + index);ArrayList.this.elementData[offset + index] = e;return oldValue;}//get方法public E get(int index) {rangeCheck(index);checkForComodification();return ArrayList.this.elementData(offset + index);}//add方法public void add(int index, E e) {rangeCheckForAdd(index);checkForComodification();parent.add(parentOffset + index, e);this.modCount = parent.modCount;this.size++;}//remove方法public E remove(int index) {rangeCheck(index);checkForComodification();E result = parent.remove(parentOffset + index);this.modCount = parent.modCount;this.size--;return result;}}該SubLsit是ArrayList的內部類,它與ArrayList一樣,都是繼承AbstractList和實現RandomAccess接口。同時也提供了get、set、add、remove等list常用的方法。但是它的構造函數有點特殊,在該構造函數中有兩個地方需要注意:
? ? ? ? (1)this.parent = parent;而parent就是在前面傳遞過來的list,也就是說this.parent就是原始list的引用。
? ? ? ? (2)this.offset = offset + fromIndex;this.parentOffset = fromIndex;。同時在構造函數中它甚至將modCount(fail-fast機制)傳遞過來了。
我們再看get方法,在get方法中return ArrayList.this.elementData(offset + index);這段代碼可以清晰表明get所返回就是原列表offset + index位置的元素。同樣的道理還有add方法里面的:
parent.add(parentOffset + index, e); this.modCount = parent.modCount;?remove方法里面的:
E result = parent.remove(parentOffset + index); this.modCount = parent.modCount;誠然,到了這里我們可以判斷:subList返回的SubList同樣也是AbstractList的子類,同時它的方法如get、set、add、remove等都是在原列表上面做操作,它并沒有像subString一樣生成一個新的對象。所以subList返回的只是原列表的一個視圖,它所有的操作最終都會作用在原列表上。
那么從這里的分析我們可以得出上面的結果應該恰恰與我們上面的答案相反:
list1 == list2:false list1 == list3:trueJava集合細節3.1:subList返回的只是原始列表的一個視圖,他所有的操作最終都會作用在原列表上。
2、subList 生成子列表后,不要試圖去操作原列表:
從上面我們知道subList生成的子列表只是原列表的一個視圖而已,如果我們操作子列表它產生的作用都會在原列表上面表現,但是如果我們操作原列表會產生什么情況呢?
public static void main(String[] args) {List<Integer> list1 = new ArrayList<Integer>();list1.add(1);list1.add(2);//通過subList生成一個與list1一樣的列表 list3List<Integer> list3 = list1.subList(0, list1.size());//修改list3list1.add(3);System.out.println("list1'size:" + list1.size());System.out.println("list3'size:" + list3.size());}該實例如果不產生意外,那么他們兩個list的大小都應該都是3,但是偏偏事與愿違,事實上我們得到的結果是這樣的:
list1'size:3 Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$SubList.checkForComodification(Unknown Source)at java.util.ArrayList$SubList.size(Unknown Source)at com.chenssy.test.arrayList.SubListTest.main(SubListTest.java:17)list1正常輸出,但是list3就拋出ConcurrentModificationException異常,看過我另一篇博客的同仁肯定對這個異常非常清楚,fail-fast?不錯就是fail-fast機制,在fail-fast機制中,LZ花了很多力氣來講述這個異常,所以這里LZ就不對這個異常多講了(更多請點這里:Java集合篇:fail-fast機制 與 fail-safe)。我們再看size方法:
public int size() {checkForComodification();return this.size;}size方法首先會通過checkForComodification驗證,然后再返回this.size。
private void checkForComodification() {if (ArrayList.this.modCount != this.modCount)throw new ConcurrentModificationException();}該方法表明當原列表的modCount與this.modCount不相等時就會拋出ConcurrentModificationException。同時我們知道modCount 在new的過程中 “繼承”了原列表modCount,只有在修改該列表(子列表)時才會修改該值(先表現在原列表后作用于子列表)。而在該實例中我們是操作原列表,原列表的modCount當然不會反應在子列表的modCount上啦,所以才會拋出該異常。
?對于子列表視圖,它是動態生成的,生成之后就不要操作原列表了,否則必然都導致視圖的不穩定而拋出異常。最好的辦法就是將原列表設置為只讀狀態,要操作就操作子列表:
//通過subList生成一個與list1一樣的列表 list3 List<Integer> list3 = list1.subList(0, list1.size());//對list1設置為只讀狀態 list1 = Collections.unmodifiableList(list1);Java集合細節3.2:生成子列表后,不要試圖去操作原列表,否則會造成子列表的不穩定而產生異常。
3、推薦使用subList處理局部列表:
在開發過程中我們一定會遇到這樣一個問題:獲取一堆數據后,需要刪除某段數據。例如,有一個列表存在1000條記錄,我們需要刪除100-200位置處的數據,可能我們會這樣處理:
for(int i = 0 ; i < list1.size() ; i++){if(i >= 100 && i <= 200){list1.remove(i);/** 當然這段代碼存在問題,list remove之后后面的元素會填充上來,* 所以需要對i進行簡單的處理,當然這個不是這里討論的問題。*/} }這個應該是我們大部分人的處理方式吧,其實還有更好的方法,利用subList。在前面LZ已經講過,子列表的操作都會反映在原列表上。所以下面一行代碼全部搞定:
list1.subList(100, 200).clear();簡單而不失華麗!!!!!
Java集合細節3.3:使用subList處理局部列表。
?
原文轉自:
https://blog.csdn.net/chenssy/article/details/38373833
https://blog.csdn.net/chenssy/article/details/38373877
https://blog.csdn.net/chenssy/article/details/44102915
?
總結
以上是生活随笔為你收集整理的Java集合篇:集合细节:为集合指定初始容量、asList的缺陷、subList的缺陷的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java集合篇:fail-fast机制
- 下一篇: Java设计模式之创建型:单例模式