1. 程式人生 > >java集合學習之List(二)隨機訪問RandomAccess介面和ArrayList和LinkedList遍歷效能問題

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 &lt; 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複製的!!一定要看全啊....還是資料結構不熟悉