天天看點

帶你走進Java集合_ArrayList源碼深入分析2

上一篇文章我們對ArrayList的屬性、構造方法、增删改查方法進行了詳細的了解,也解讀了為什麼在多線程下ArrayList不能作為共享變量的原因,本篇文章主要介紹ArrayList的兩個功能相似的方法。加入我們定義list

第一個方法:removeAll

public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);----------------------❶
        return batchRemove(c, false);-------------------❷
    }
           

❶就是判斷給出的集合是否為null,如果為null,則會抛出NullPointerException異常。

removeAll方法就是list移除所有包含在c中的元素

舉例說明:

List<Integer>list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        Collection<Integer>c = new ArrayList<Integer>();
        c.add(2);
        c.add(4);
        c.add(6);
        list.removeAll(c);
        for(Integer i:list){
            System.out.println(i);
        }
           

運作結果:

帶你走進Java集合_ArrayList源碼深入分析2

第二個方法:retainAll

public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);------------------❸
    }
           

retainAll方法就是list移除所有不包含在c中的元素

舉例說明:

List<Integer>list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        Collection<Integer>c = new ArrayList<Integer>();
        c.add(2);
        c.add(4);
        c.add(6);
        list.retainAll(c);
        for(Integer i:list){
            System.out.println(i);
        }
           

運作結果:

帶你走進Java集合_ArrayList源碼深入分析2

我們從兩個示例中可以看出,removeAll就是兩個集合的差集,retainAll就是兩個集合的交集。

我們知道這兩個方法的作用後,我們就開始從源碼中分析是怎樣實作的,因為removeAll和retainAll兩個方法最終的實作都是調用batchRemove方法,差別就是第二個參數不同,removeAll傳遞的是false,retainAll傳遞的是true。

removeAll-->list.batchRemove(c,false)
retainAll-->list.batchRemove(c,true)
           

接下來我們看看batchRemove到底怎樣實作的

private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)---------------❹
                    elementData[w++] = elementData[r];
        } finally {
            if (r != size) {
                System.arraycopy(elementData, r,elementData, w,size - r);
                w += size - r;
            }
            if (w != size) {
                // 下面的循環,把元素指派null,讓GC回收
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
           

從batchRemove的源碼中,我們大概可以看出主要的邏輯在

for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)---------------❹
                    elementData[w++] = elementData[r];
           

我們以上面兩個示例帶入的方法幫大家去了解(c包含2、4,6)

對于removeAll:

1)當r=0時,elementData[0]=1,c.contains(1)==false  等式成立  elementData[w++]=elementData[0]=1 此時w=1

2)當r=1時,elementData[1]=2,c.contains(2)==false  等式不成立 此時w=1

3)當r=2時,elementData[2]=3,c.contains(3)==false  等式成立  elementData[w++]=element[2]=3,此時w=2

4)當r=3時,elementData[3]=4,c.contains(4)==false  等式不成立 此時w=2
5)當r=4時,elementData[4]=5,c.contains(5)==false  等式成立  elementData[w++]=element[4]=5,此時w=3

6)當r=5時,elementData[5]=6,c.contains(6)==false  等式不成立 此時w=3
           

是以循環結束後局部變量elementData={1,3,5,4,5,6},此時r=6,w=3,然後在看finally中的代碼

if (r != size) {
    System.arraycopy(elementData, r, elementData, w, size - r);
    w += size - r;
}
           

上面的這段代碼是判斷是否出現過異常,如果出現了異常,則r一定等于size,如果,如果沒有任何的問題,程式是進不去這段代碼的,那麼現在elementData={1,3,5,4,5,6},而我們知道,removeAll後的list應該是{1,3,5},是以接下來的代碼就應該是把4,5,6删除掉,Java源碼是這樣實作的:

if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
           

我們知道此時w=3,一定會進入這個方法,是以從下标為3開始将元素指派null,被GC回收,至此elementDatas還有1,3,5三個元素。是以就證明把包含c的元素去掉了。

對于retainAll:

1)當r=0時,elementData[0]=1,c.contains(1)==true  等式不成立 此時w=0
2)當r=1時,elementData[1]=2,c.contains(2)==true  等式成立  elementData[w++]=elementData[1]=2 此時w=1
3)當r=2時,elementData[2]=3,c.contains(3)==true  等式不成立 此時w=1
4)當r=3時,elementData[3]=4,c.contains(4)==true  等式成立  elementData[w++]=element[3]=4,此時w=2
5)當r=4時,elementData[4]=5,c.contains(5)==true  等式不成立 此時w=3
6)當r=5時,elementData[5]=6,c.contains(6)==true  等式成立  elementData[w++]=element[5]=6,此時w=3
           

是以retainAll和removeAll恰好相反。此時elementDatas={2,4,6,4,5,6},通過從下标3開始将元素指派null,被GC回收,至此elementDatas還剩2,4,6三個元素

從上面的分析,大家是不是非常清楚removeAll和retainAll的作用了,removeAll可能用的比較多,而retainAll用的相對少點,如果不了解内部的機制,即使現在記住了兩個的作用和差別,但是一段時間後就會忘掉,這樣分析後是不是清晰多了。

總結:

1)removeAll是取兩個集合的差集

2)retainAll是取兩個集合的交集

本篇文章就先寫到這,接下來會對ArrayList的疊代器進行詳細的解析,請持續關注