咱們以前在分析那三個集合源碼的時候,曾經說到:ArrayList和Vector繼承了RandomAccess
接口,可是LinkedList並無,咱們還知道繼承了這個接口,就意味着其中元素支持快速隨機訪問(fast random access)。數組
出於好奇,我特地去查看了RandomAccess的官方文檔,讓我以爲異常驚訝的是!這個接口中啥也沒有!是真的奇怪!(事實上和它相似的還有Cloneable
和java.io.Serializable
,這倆以後會探討)只留下一串冰冷的英文。app
Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.dom
哎,無論他,翻譯就完事了,今天的生活也是鬥志滿滿的搬運工生活呢!ide
我用我本身的語言組織一下:工具
咱們知道,ArrayList和Vector底層基於數組實現,內存中佔據連續的存儲空間,每一個元素的下標實際上是偏移首地址的偏移量,這樣子查詢元素只須要根據:元素地址 = 首地址+(元素長度*下標),就能夠迅速完成查詢,一般只須要花費常數的時間,因此它們理應實現該接口。可是鏈表不一樣,鏈表依據不一樣節點之間的地址相互引用完成聯繫,自己不要求地址連續,查詢的時候須要遍歷的過程,這樣子會致使,在數據量比較大的時候,查詢元素消耗的時間會很長。oop
RandomAccess接口的全部實現類:
ArrayList, AttributeList, CopyOnWriteArrayList, RoleList, RoleUnresolvedList, Stack, Vector性能
the given list is an instanceof this interface before applying an algorithm that would provide poor performance if it were applied to a sequential access list, and to alter their behavior if necessary to guarantee acceptable performance.學習
能夠經過xxList instanceof RandomAccess)
判斷該列表是否爲該接口的實例,若是是順序訪問的列表(如LinkedList),就不該該經過下標索引的方式去查詢其中的元素,這樣效率會很低。測試
/*for循環遍歷*/ for (int i=0, n=list.size(); i < n; i++) list.get(i); /*Iterator遍歷*/ for (Iterator i=list.iterator(); i.hasNext();) i.next();
對於實現RandomAccess接口,支持快速隨機訪問的列表來講,for循環+下標索引遍歷的方式比迭代器遍歷的方式要更快。
對此,咱們是否能夠猜測,若是是LinkedList這樣並不支持隨即快速訪問的列表,是不是Iterator更快呢?因而咱們進行一波嘗試:
/*for循環遍歷的測試*/ public static void forTest(List list){ long start = System.currentTimeMillis(); for (int i = 0,n=list.size(); i < n; i++) { list.get(i); } long end = System.currentTimeMillis(); long time = end - start; System.out.println(list.getClass()+" for循環遍歷測試 cost:"+time); } /*Iterator遍歷的測試*/ public static void iteratorTest(List list){ long start = System.currentTimeMillis(); Iterator iterator = list.iterator(); while(iterator.hasNext()){ iterator.next(); } long end = System.currentTimeMillis(); long time = end-start; System.out.println(list.getClass()+"迭代器遍歷測試 cost:"+time); }
public static void main(String[] args) { List<Integer> linkedList = new LinkedList<>(); List<Integer> arrayList = new ArrayList<>(); /*ArrayList不得不加大數量觀察它們的區別,其實差異不大*/ for (int i = 0; i < 5000000; i++) { arrayList.add(i); } /*LinkedList 這個量級就能夠體現比較明顯的區別*/ for(int i = 0;i<50000;i++){ linkedList.add(i); } /*方法調用*/ forTest(arrayList); iteratorTest(arrayList); forTest(linkedList); iteratorTest(linkedList); }
咱們能夠發現:
上面也提到了, 這個空空的接口就是承擔着標記的職責(Marker),標記着是否支持隨機快速訪問,若是不支持的話,還使用索引來遍歷的話,效率至關之低。既然有標記,那咱們必定有方法去區分標記。這時,咱們須要使用instanceof
關鍵字幫助咱們作區分,以選擇正確的訪問方式。
public static void display(List<?> list){ if(list instanceof RandomAccess){ //若是支持快速隨機訪問 forTest(list); }else { //不支持快速隨機訪問,就用迭代器 iteratorTest(list); } }
再進行一波測試看看,他倆都找到了本身的歸宿:
事實上,集合工具類Collections
中有許多操做集合的方法,咱們隨便舉一個從前日後填充集合的方法:
public static <T> void fill(List<? super T> list, T obj) { int size = list.size(); if (size < FILL_THRESHOLD || list instanceof RandomAccess) { //for遍歷 for (int i=0; i<size; i++) list.set(i, obj); } else { //迭代器遍歷 ListIterator<? super T> itr = list.listIterator(); for (int i=0; i<size; i++) { itr.next(); itr.set(obj); } } }
還有許多這樣的方法,裏面有許多值得學習的地方。我是一個正在學習Java的小白,也許個人知識還未有深度,可是我會努力把本身學習到的作一個體面的總結。對了,若是文章有理解錯誤,或敘述不清之處,還望你們評論區批評指正。
參考資料:JDK1.8官方文檔