java集合學習之List(二)隨機訪問RandomAccess介面和ArrayList和LinkedList遍歷效能問題
ArrayList這個類是實現了RandomAccess介面的,RandomAccess介面和Serializable介面一樣都是沒有方法或者欄位的,像是一個標誌,
RandomAccess介面文件說明的是:Marker interface used by <tt>List</tt> implementations to indicate thatthey support fast (generally constant time) random access. [(標記介面用於List繼承表名支援快速隨機訪問)]
這個介面在Collections類中用的很多,用於判斷是否是RandomAccess介面的例項(其實就是多型的應用)[list instanceof RandomAccess]根據是否實現該介面,來選用不同的算
貼原始碼註釋+自己的翻譯:
/** * Marker interface used by <tt>List</tt> 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. * * 標記該介面用來被List實現指示他們支援快速隨機訪問. 這個介面的主要目的就是允許一般演算法去修改他們的 * 行為以提供更好的表現:當被應用與隨機或者順序方式列表時. * * <p>The best algorithms for manipulating random access lists (such as * <tt>ArrayList</tt>) can produce quadratic behavior when applied to * sequential access lists (such as <tt>LinkedList</tt>). Generic list * algorithms are encouraged to check whether the given list is an * <tt>instanceof</tt> 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. * * 操作隨機訪問列表的lists時比如ArrayList的最好的演算法當應用於順序訪問列表如LinkedList時,可能會產生二次項行為 * (媽的,什麼是二次項行為啊!韓老師說(a+b)^2展開就是) * 在應用演算法之前通用列表演算法鼓勵去檢查這個list是否是該介面的例項,如果應用於順序訪問列表時, * 則會提供較差的效能,並且在必要時去警告他們行為來保證可接受的效能. * * <p>It is recognized that the distinction between random and sequential * access is often fuzzy. For example, some <tt>List</tt> implementations * provide asymptotically linear access times if they get huge, but constant * access times in practice. Such a <tt>List</tt> implementation * should generally implement this interface. As a rule of thumb, a * <tt>List</tt> implementation should implement this interface if, * for typical instances of the class, this loop: * <pre> * for (int i=0, n=list.size(); i < n; i++) * list.get(i); * </pre> * runs faster than this loop: * <pre> * for (Iterator i=list.iterator(); i.hasNext(); ) * i.next(); * </pre> * * 我們意識到在隨機訪問和順序訪問的區別通常是模糊的,比如,列表很大時,一些list的實現提供了 * 漸進線性訪問時間,但在實際中時間是不變的.這樣的List應該實現該介面, * 作為一個經驗法則,一個介面應該儘量實現這個這個介面, * 對於這個介面的實現使用for迴圈筆使用iterator迴圈更快 * * <p>This interface is a member of the * <a href="{@docRoot}/../technotes/guides/collections/index.html"> * Java Collections Framework</a>. * * @since 1.4 */
以上是對於RandomAccess介面的描述,一句話概括就是,在使用迴圈遍歷List的時候應該判斷下這個集合是否是RandomAccess的例項,如果是就是用for迴圈來操作,如果不是就是使用iterator迭代器來操作.可是為什麼要這麼做的?這要從資料結構的角度來說了
首先看一下實現了RandomAccess介面的ArrayList的介面,一般人都知道ArrayList就是一個數組
然後看下沒有實現RandomAccess介面的LinkedList,顧名思義就是一個連結串列
先看一下連結串列和陣列的區別:
陣列像是身上都有了編號排成一排的人,你要找第幾個直接根據編號去找,就是所謂的根據下標去查詢,所以在查詢的時候很快,但是如果要插入就變得很慢了,你還要給插入位置之後的其他人重新定義編號,刪除同理,效率肯定慢.
連結串列就像是牽手站成一排的人,你要找第幾個就要一個一個取數,從1數到n,但是插入的時候直接把人分開,重新牽手就可以,無需編號,刪除同理.
所以如果用for迴圈遍歷的話,貼個程式碼看看:
/**
* Collections 測試RandomAccess隨機訪問速度
*/
List<String> l = new ArrayList<String>();
List<String> _l = new LinkedList<String>();
for(int i=0;i<100000;i++){
l.add(String.valueOf(i));
_l.add(String.valueOf(i));
}
long startTime = System.currentTimeMillis();
for(int i=0;i<l.size();i++){
l.get(i);
}
System.out.println("count:"+(System.currentTimeMillis()-startTime));
startTime = System.currentTimeMillis();
for(int i=0;i<_l.size();i++){
_l.get(i);
}
System.out.println("count:"+(System.currentTimeMillis()-startTime));
startTime = System.currentTimeMillis();
for(Iterator<String> it=_l.iterator();it.hasNext();){
it.next();
}
System.out.println("count:"+(System.currentTimeMillis()-startTime));
輸出結果就是:(ArrayList)count:0 (LinkedList)count:14760 (LinkedList)count:10
驚訝吧!開始分析!ArrayList不用管,就是根據下邊直接去查,看下普通for迴圈下LinkedList為啥賊雞兒慢!
看一下LinkedList的get方法
判斷index在整體的前部還是後部,在前部就從前往後遍歷,在後部就從後向前遍歷,每次都這樣從頭遍歷,能不慢麼,演算法複雜度有n^2了吧
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
然後看下LinkedList的Iterator方法為啥挺快呢?通過實現類看到:
具體參考java集合學習之List(三)以LinkedList為例,debug看下迭代器的實現.
返回一個迭代器,然後判斷是否有下一個節點,有的話就得到當前節點的next,不必再去多於的迴圈遍歷.所以速度肯定很快.
-----------------------------------------------------------------------------------------------------
之前我的疑問在於這個LinkedList的Node節點是如何賦值的,今天又仔細看了下原始碼才他媽的看到的add的時候給Node複製的!!一定要看全啊....還是資料結構不熟悉