支持快速失敗的ArrayList:batchRemove

看了ArrayList源碼後,發現batchRemove還是較爲複雜的,所以在這裏寫篇文章記錄一下,以免以後再看時又頭疼,哈哈哈。

先以自己的理解,弄一個簡單版的,由淺入深,幹掉他,如果讀者發現有什麼不正確的或有什麼問題請及時指出哦。

先使用兩個list模擬數組,從list1中移除list2存在的元素。

@Test
    public void testBatchRemove() { // 遍歷list1,從list1中移除list2存在的元素
        // 初始化數據
        List<Integer> list1 = new ArrayList<>(10);
        for (int i = 0; i < 5; i++) {
            list1.add(i);
        }

        // 初始化數據
        List<Integer> list2 = new ArrayList<>(10);
        list2.add(2);
        // 修改list數據只通過set方法,r和w作爲數組的索引
        int r = 0, w = 0; // r:讀, w:寫

        /**
         * 從下面例子中可以看出
         * 在第一輪和第二輪循環中,r和w是同步增加的
         * 讀會不停的往後讀(即 r),但是寫只會寫入不被刪除的數據
         * example
         * list1 = [0, 1, 2, 3, 4]
         * list2 = [2]
         * 第一輪循環結束 r = 1, w = 1 list1 = [0, 1, 2, 3, 4]
         * 第二輪循環結束 r = 2, w = 2 list1 = [0, 1, 2, 3, 4]
         * 第三輪循環結束 r = 3, w = 2 list1 = [0, 1, 2, 3, 4] 注:第三輪將不會進入if,導致w的更新比r慢一步
         * 第四輪循環結束 r = 4, w = 3 list1 = [0, 1, 3, 3, 4] 注:元素 [2] 已被刪除
         * 第五輪循環結束 r = 5, w = 4 list1 = [1, 3, 4, 5, 5] 注:後面的元素將會向前推進一步
         */
        for(;r < list1.size();r++) {
            // 如果不包含,說明該位置的元素不能修改,則修改讀(r)和寫(w)的索引
            // 如果包含,說明該位置的元素需要修改,則只修改讀(r)的索引
            if (!list2.contains(list1.get(r))) { // 搞清楚這個if就行啦!打斷點看會舒服很多
                list1.set(w++, list1.get(r));
            }
            // (r + 1)爲了模擬for循環結束一輪的場景,因爲打印的時候r不會++,
            System.out.println(list1 + " w: " + w + " r: " + (r + 1));
        }
        // 只需要截取掉從0~w的元素即可
        // 那怎麼知道修改了多少次呢? 在截取之前使用 list.size() - w 即可知道修改了多少次元素 5 - 4 = 1
        List<Integer> integers = list1.subList(0, w);
        System.out.println(integers);
    }

那問題來了,剛剛是移除list2中存在的元素,那要是想保留list2中存在的元素,怎麼辦?那難道還要再寫一個方法嗎?當然不是啦,只需要變一下就行啦~

增加變量 

complement 是否補充數組中的數據,false則刪除,true則保留

來了來了,新版本

/**
     * 遍歷list1,從list1中移除或只保留list2存在的元素
     */
    @Test
    public void testComplement() {
        // 是否補充數組中的數據,false則刪除,true則保留
        boolean complement = false;
        // 初始化數據
        List<Integer> list1 = new ArrayList<>(10);
        for (int i = 0; i < 5; i++) {
            list1.add(i);
        }

        // 初始化數據
        List<Integer> list2 = new ArrayList<>(10);
        list2.add(2);
        // 修改list數據只通過set方法,r和w作爲數組的索引
        int r = 0, w = 0; // r:讀, w:寫

        for(;r < list1.size();r++) {
            if (list2.contains(list1.get(r)) == complement) { // 搞清楚這個if就行啦!打斷點看會舒服很多
                list1.set(w++, list1.get(r));
            }
            // (r + 1)爲了模擬for循環結束一輪的場景,因爲打印的時候r不會++,
            System.out.println(list1 + " w: " + w + " r: " + (r + 1));
        }

        List<Integer> integers = list1.subList(0, w);
        System.out.println(integers);
    }

繼續。恩,是的,問題他又接着來了,現在有兩個數組

list1 = [1, 2, 3, 4, 5, 6, 7]

list2 = [5, 6, 7]

像這樣的,list1要刪除list2中存在的元素,那list1中開頭的第1~4個元素都不需要修改。現在這小數據不需要修改,那在生產環境中誰知道呢,哈哈,所以開頭的優化下撒。

最終版本來了,這是你****的全新版本

/**
     * 遍歷list1,從list1中移除或只保留list2存在的元素
     *  優化開頭的操作
     *      list1.set(w++, list1.get(r));
     */
    @Test
    public void testOptimize() {
        // 是否補充數組中的數據,false則刪除,true則保留
        boolean complement = false;
        // 初始化數據
        List<Integer> list1 = new ArrayList<>(10);
        for (int i = 0; i < 5; i++) {
            list1.add(i);
        }

        // 初始化數據
        List<Integer> list2 = new ArrayList<>(10);
        list2.add(3);
        // 修改list數據只通過set方法,r和w作爲數組的索引
        int r = 0, w = 0; // r:讀, w:寫

        /**
         * 該for循環用於省略數組開頭的賦值操作 :
         *      list1.set(w++, list1.get(r));
         */
        for (;r < list1.size(); r++) {
            // 如果沒有要刪除的操作,則直接返回即可,這樣就完全省略了賦值操作
            //      list1.set(w++, list1.get(r));
            if (r == list1.size()) {
                return;
            }
            // 當需要刪除是,則跳出
            if (list2.contains(list1.get(r)) != complement) {
                break;
            }
            // r 和 w同步增加
            w++;
        }

        // 經過上個for循環的優化,該for循環的第一個操作必定的刪除操作
        // 由於r當前位置已經確定要被刪除,所以r = r + 1,讀取下一個
        for(r = r + 1;r < list1.size();r++) {
            if (list2.contains(list1.get(r)) == complement) { // 搞清楚這個if就行啦!打斷點看會舒服很多
                list1.set(w++, list1.get(r));
            }
            // (r + 1)爲了模擬for循環結束一輪的場景,因爲打印的時候r不會++,
            System.out.println(list1 + " w: " + w + " r: " + (r + 1));
        }

        List<Integer> integers = list1.subList(0, w);
        System.out.println(integers);
    }

其實到這也差不多了,現原形!以下是JDK13的源碼。

boolean batchRemove(Collection<?> c, boolean complement,
                        final int from, final int end) {
        // 校驗集合是否爲空
        Objects.requireNonNull(c);
        final Object[] es = elementData;
        // 優化
        int r;
        // Optimize for initial run of survivors
        for (r = from;; r++) {
            if (r == end)
                return false;
            if (c.contains(es[r]) != complement)
                break;
        }
        // 和優化版本的代碼同理 r = r + 1
        int w = r++;
        try {
            for (Object e; r < end; r++)
                if (c.contains(e = es[r]) == complement)
                    es[w++] = e;
        } catch (Throwable ex) {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            System.arraycopy(es, r, es, w, end - r);
            w += end - r;
            throw ex;
        } finally {
            // 計算修改次數,應該是用來觸發快速失敗的
            modCount += end - w;
            // 收尾收尾
            shiftTailOverGap(es, w, end);
        }
        return true;
    }

之前看了JDK8的代碼,發現有點不一樣,無非是少了優化階段,然後收尾階段也沒有封裝,就這樣撒。大家有什麼問題的歡迎下面留言哦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章