在學習 Java
集合時, 最早學習的即是 List
中的 ArrayList
和 LinkedList
, 學習集合很關鍵的是學習其源碼, 瞭解底層實現方式, 那麼今天就講講 ArrayList
實現的一個接口 RandomAccess
。java
查看 ArrayList
的源碼, 發現它實現了 RandomAccess
這個接口, 出於好奇點進去看看, 結果發現這接口是空的, 這固然引起了更大的好奇心:這空架子到底有何用?算法
JDK 官方文檔是不可少的工具, 先看看它是怎麼說的:RandomAccess
是 List
實現所使用的標記接口,用來代表其支持快速(一般是固定時間)隨機訪問。此接口的主要目的是容許通常的算法更改其行爲,從而在將其應用到隨機或連續訪問列表時能提供良好的性能。數組
標記接口(Marker):這就說明了 RandomAccess
爲空的緣由,這個接口的功能僅僅起到標記的做用。dom
這不是與序列化接口 Serializable
差很少嗎? 只要你認真觀察, 其實不僅這一個標記接口, 實際上 ArrayList
還實現了另外兩個這樣的空接口:工具
Cloneable
接口 :實現了 Cloneable
接口,以指示 Object.clone()
方法能夠合法地對該類實例進行按字段複製。 若是在沒有實現 Cloneable
接口的實例上調用 Object
的 clone
方法,則會致使拋出 CloneNotSupportedException
異常。oop
Serializable
接口: 類經過實現 java.io.Serializable
接口以啓用其序列化功能。未實現此接口的類將沒法使其任何狀態序列化或反序列化。性能
標記接口都有什麼做用呢? 繼續討論 RandomAccess 的做用,其餘兩個在此不做討論。學習
若是 List
子類實現了 RandomAccess
接口,那表示它能快速隨機訪問存儲的元素, 這時候你想到的多是數組, 經過下標 index
訪問, 實現了該接口的 ArrayList
底層實現就是數組, 一樣是經過下標訪問, 只是咱們須要用 get()
方法的形式 , ArrayList
底層仍然是數組的訪問形式。測試
同時你應該想到鏈表, LinkedList
底層實現是鏈表, LinkedList
沒有實現 RandomAccess
接口,發現這一點就是突破問題的關鍵點。spa
數組支持隨機訪問, 查詢速度快, 增刪元素慢; 鏈表支持順序訪問, 查詢速度慢, 增刪元素快。因此對應的 ArrayList
查詢速度快,LinkedList
查詢速度慢, RandomAccess
這個標記接口就是標記可以隨機訪問元素的集合, 簡單來講就是底層是數組實現的集合。
爲了提高性能,在遍歷集合前,咱們即可以經過 instanceof
作判斷, 選擇合適的集合遍歷方式,當數據量很大時, 就能大大提高性能。
隨機訪問列表使用循環遍歷,順序訪問列表使用迭代器遍歷。
先看看 RandomAccess
的使用方式
import java.util.*;
public class RandomAccessTest {
public static void traverse(List list){
if (list instanceof RandomAccess){
System.out.println("實現了RandomAccess接口,不使用迭代器");
for (int i = 0;i < list.size();i++){
System.out.println(list.get(i));
}
}else{
System.out.println("沒實現RandomAccess接口,使用迭代器");
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
traverse(arrayList);
List<String> linkedList = new LinkedList<>();
linkedList.add("c");
linkedList.add("d");
traverse(linkedList);
}
}
複製代碼
下面咱們加入大量數據進行性能測試:
import java.util.*;
public class RandomAccessTimeTest {
//使用for循環遍歷
public static long traverseByLoop(List list){
long startTime = System.currentTimeMillis();
for (int i = 0;i < list.size();i++){
list.get(i);
}
long endTime = System.currentTimeMillis();
return endTime-startTime;
}
//使用迭代器遍歷
public static long traverseByIterator(List list){
Iterator iterator = list.iterator();
long startTime = System.currentTimeMillis();
while (iterator.hasNext()){
iterator.next();
}
long endTime = System.currentTimeMillis();
return endTime-startTime;
}
public static void main(String[] args) {
//加入數據
List<String> arrayList = new ArrayList<>();
for (int i = 0;i < 30000;i++){
arrayList.add("" + i);
}
long loopTime = RandomAccessTimeTest.traverseByLoop(arrayList);
long iteratorTime = RandomAccessTimeTest.traverseByIterator(arrayList);
System.out.println("ArrayList:");
System.out.println("for循環遍歷時間:" + loopTime);
System.out.println("迭代器遍歷時間:" + iteratorTime);
List<String> linkedList = new LinkedList<>();
//加入數據
for (int i = 0;i < 30000;i++){
linkedList.add("" + i);
}
loopTime = RandomAccessTimeTest.traverseByLoop(linkedList);
iteratorTime = RandomAccessTimeTest.traverseByIterator(linkedList);
System.out.println("LinkedList:");
System.out.println("for循環遍歷時間:" + loopTime);
System.out.println("迭代器遍歷時間:" + iteratorTime);
}
}
複製代碼
結果:
ArrayList: for 循環遍歷時間: 3 迭代器遍歷時間: 7
LinkedList: for 循環遍歷時間: 2435 迭代器遍歷時間: 3
根據結果咱們能夠得出結論: ArrayList
使用 for 循環遍歷優於迭代器遍歷 LinkedList
使用 迭代器遍歷優於 for 循環遍歷
根據以上結論即可利用 RandomAccess
在遍歷前進行判斷,根據 List
的不一樣子類選擇不一樣的遍歷方式, 提高算法性能。
學習閱讀源碼, 發現底層實現的精妙之處, 改變本身的思惟, 從每個小細節提高代碼的性能。