Collections.reverse 代碼思考-超越昨天的自己系列(13)


點進Collections.reverse的代碼瞄了眼,然后就開始了一些基礎知識的收集。

現在發現知道的越多,知道不知道的越多。

列幾個記錄下:

reverse方法源碼:

 
/**
     * Reverses the order of the elements in the specified list.<p>
     *
     * This method runs in linear time.
     *
     * @param  list the list whose elements are to be reversed.
     * @throws UnsupportedOperationException if the specified list or
     *         its list -iterator does not support the <tt>set</tt> operation.
     */
    public static void reverse (List<?> list) {
        int size = list.size();
        if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
            for ( int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
                swap(list, i, j);
        } else {
            ListIterator fwd = list.listIterator();
            ListIterator rev = list.listIterator(size);
            for ( int i=0, mid=list.size()>>1; i<mid; i++) {
           Object tmp = fwd.next();
                fwd.set(rev.previous());
                rev.set(tmp);
            }
        }
    }

 

1,首先看見RandomAccess

List 實現所使用的標記接口,用來表明其支持快速(通常是固定時間)隨機訪問。此接口的主要目的是允許一般的算法更改其行為,從而在將其應用到隨機或連續訪問列表時能提供良好的性能。

將操作隨機訪問列表的最佳算法(如 ArrayList)應用到連續訪問列表(如 LinkedList)時,可產生二次項的行為。如果將某個算法應用到連續訪問列表,那么在應用可能提供較差性能的算法前,鼓勵使用一般的列表算法檢查給定列表是否為此接口的一個 instanceof,如果需要保證可接受的性能,還可以更改其行為。

現在已經認識到,隨機和連續訪問之間的區別通常是模糊的。例如,如果列表很大時,某些 List 實現提供漸進的線性訪問時間,但實際上是固定的訪問時間。這樣的 List 實現通常應該實現此接口。實際經驗證明,如果是下列情況,則 List 實現應該實現此接口,即對於典型的類實例而言,此循環:

   for (int i=0, n=list.size(); i < n; i++)
         list.get(i);
 

的運行速度要快於以下循環:

     
for (Iterator i=list.iterator(); i.hasNext(); )
         i.next();

 

2,標記接口(marker interface

  又叫Tagging Interfaces。標識接口是沒有任何方法和屬性的接口。標識接口不對實現它的類有任何語義上的要求,它僅僅表明實現它的類屬於一個特定的類型。常見的有Serializable  Cloneable    Remote    EventListener 
你當然可以任意定義沒有任何方法和屬性的接口,但肯定不應該稱為標識接口,因為JDK里的“標識接口”不光是“只有個名字”這么簡單,更重要的是,實現這些標志接口的類,確實多了功能,盡管你看不到這些功能是怎么實現的。比如,Serializable,實現了這個接口,那這個序列化的工作,到底是誰做的那?Cloneable,實現了這個接口,並在重寫的clone()方法里只是調用了一下super.clone(),就產生了一個全新的對象,要知道Object里的clone()方法是沒有任何實現的,這個克隆的工作,到底是誰完成的那?JVM or Reflection,但是你看不到它們。 
拿java.io.Serializable接口作為例子來說明一下。 如果存在一個對象,它實現了java.io.Serializable接口,由於接口本身沒有定義任何方法行為。所以實現接口的行為由java編譯器來完成。當一個java類實現了這個接口,在編譯過程中,java編譯器會發現這個類的對象是屬於java.io.Serializable這種類型,那么編譯器就會為這個特殊的類實現序列化所要求的特殊的行為,使得該類的對象可以在不同虛擬機之間傳遞。 所以說,我們需要有一個標記的東西 來通知java編譯器這個特殊的屬性,我們就定義了標識接口。
關於標志接口的對於錯,爭論是有的: 
標志接口是對接口的誤用,應該被避免,使用標志接口的類,都是一些相當古老的類。Java 5 加入 注解 特性后,標志接口更不會再有出現的必要。 
使用注解來標識類,方法等的特定標簽更加靈活,這又是一個可以擴展學習的點。
 
3,Iterator 和 ListIterator
想到個問題,比如list個通過get獲取其中元素,為什么要有迭代器呢?
Iterator模式是用於遍歷集合類的標准訪問方法。它可以把訪問邏輯從不同類型的集合類中抽象出來,從而避免向客戶端暴露集合的內部結構。
 

例如,如果沒有使用Iterator,遍歷一個數組的方法是使用索引:
        for(int i=0; i<array.size(); i++) { ... get(i) ... } 
    客戶端都必須事先知道集合的內部結構,訪問代碼和集合本身是緊耦合,無法將訪問邏輯從集合類和客戶端代碼中分離出來,每一種集合對應一種遍歷方法,客戶端代碼無法復用。
  更恐怖的是,如果以后需要把ArrayList更換為LinkedList,則原來的客戶端代碼必須全部重寫。
為解決以上問題,Iterator模式總是用同一種邏輯來遍歷集合:

         for(Iterator it = c.iterater(); it.hasNext(); ) { ... }

  奧秘在於客戶端自身不維護遍歷集合的"指針",所有的內部狀態(如當前元素位置,是否有下一個元素)都由Iterator來維護,而這個Iterator由集合類通過工廠方法生成,因此,它知道如何遍歷整個集合。

  客戶端從不直接和集合類打交道,它總是控制Iterator,向它發送"向前","向后","取當前元素"的命令,就可以間接遍歷整個集合
 

ListIterator可以定位當前的索引位置,nextIndex()和previousIndex()可以實現。Iterator 沒有此功能。

四、都可實現刪除對象,但是ListIterator可以實現對象的修改,set()方法可以實現。Iterator僅能遍歷,不能修改。因為ListIterator的這些功能,可以實現對LinkedList等List數據結構的操作。
 
測試:
嘗試用ArrayList 和 linkedList 來使用兩種方式進行翻轉操作:
一種操作是使用源碼中swap的方式,一種使用ListInterator。
第一個測試結果:結果1:7656 結果2:2
可見像linkedList 這種是不肯能使用swap方式去翻轉的,代碼中也做了處理。鏈表,在使用隨機訪問每次耗時太長,導致這種結果。
public static void main(String[] args) {
        List list =new LinkedList();
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
        int size = list.size();
        long t1 = System.currentTimeMillis();
        for ( int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
            swap(list, i, j);
        long t2 = System.currentTimeMillis();
        System.out.println(t2 - t1);//結果1
        long t3 = System.currentTimeMillis();
        ListIterator fwd = list.listIterator();
        ListIterator rev = list.listIterator(size);
        for ( int i=0, mid=list.size()>>1; i<mid; i++) {
            Object tmp = fwd.next();
            fwd.set(rev.previous());
            rev.set(tmp);
        }
        
        long t4 = System.currentTimeMillis();
        System.out.println(t4 - t3);//結果2
    }
    
    public static void swap(List<?> list, int i, int j) {
        final List l = list;
        l.set(i, l.set(j, l.get(i)));
    }

那么ArrayList 使用這兩種方式的效果呢?

測試結果相差無幾,隨着增大數據量,swap要好於ListInterator,但是有時微乎其微,所以這個reverse代碼中並沒有對大數據量的ArrayList進行swap方式,減少了代碼冗余,也沒有降低什么性能。

 

 

 

---------------------------------------

還有很多擴展學習的地方,繼續前進吧。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM