JUC集合類 CopyOnWriteArraySet源碼解析 JDK8

前言

類似於上一篇講的CopyOnWriteArrayList,CopyOnWriteArraySet可以認爲是一個寫時複製的HashSet。

CopyOnWriteArraySet的底層實現完全依賴了CopyOnWriteArrayList,它持有了一個CopyOnWriteArrayList類型的成員,很多方法的實現都是直接調用CopyOnWriteArrayList的同名方法。

這意味着CopyOnWriteArraySet的底層實現不再是散列表的實現,而只是一個普通數組,只不過現在CopyOnWriteArraySet表現地像一個HashSet而已,不過這對於使用者來說已經足夠了。

JUC框架 系列文章目錄

與CopyOnWriteArrayList不同之處

CopyOnWriteArraySet的底層實現完全依賴了CopyOnWriteArrayList是可以的,但問題是CopyOnWriteArrayList允許有重複元素,但CopyOnWriteArraySet作爲一個HashSet卻不能有重複元素。

爲了解決這一問題,CopyOnWriteArrayList專門提供了addIfAbsentaddAllAbsent,以防止添加元素時會添加重複元素到裏面去。

addIfAbsent

//CopyOnWriteArraySet
    public boolean add(E e) {
        return al.addIfAbsent(e);
    }
//CopyOnWriteArrayList
    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        //indexOf返回的不是-1,說明元素是present而不是absent。即元素存在,所以直接返回false
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        //如果元素不存在,則返回addIfAbsent的返回值
            addIfAbsent(e, snapshot);
    }

addIfAbsent,簡單的說,就是隻有這個元素不存在於set中時(adsent,缺席),纔會加入該元素e,從而防止重複元素加入。

    private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
            	//如果數組成員已經改變,需要考慮當前的數組成員會不會又擁有了參數e
                int common = Math.min(snapshot.length, len);
                //檢查[0, snapshot.length)內的元素
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                //檢查[snapshot.length, len)內的元素
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

addIfAbsent(E e, Object[] snapshot)方法需要考慮數組成員已經改變,當前的數組成員會不會又重新擁有了參數e。而if (snapshot != current)分支內的代碼就是用來執行這額外的檢查,而這個檢查的手法和CopyOnWriteArrayListremove(Object o)方法幾乎一樣,在此不贅述。重點在於,if (snapshot != current)分支的目的,是爲了檢查currrent中是否擁有參數e,如果有則需要退出。

但這個手法在這裏有所簡化,還是分爲兩種情況:

  1. snapshot.length >= len
  2. snapshot.length < len

如果snapshot.length >= len,for循環會檢查到current的所有元素,並且此時common就是len,執行indexOf(e, current, common, len)時會直接返回-1。

如果snapshot.length < len,for循環只能檢查到[0, snapshot.length)範圍內的元素,剩下[snapshot.length, len)範圍交給indexOf(e, current, common, len)檢查。

如果if (snapshot != current)分支執行完畢都沒有返回,則說明current中確實沒有參數e

addAllAbsent

//CopyOnWriteArraySet
    public boolean addAll(Collection<? extends E> c) {
        return al.addAllAbsent(c) > 0;
    }
//CopyOnWriteArrayList
    public int addAllAbsent(Collection<? extends E> c) {
        Object[] cs = c.toArray();
        if (cs.length == 0)
            return 0;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            int added = 0;
            // uniquify and compact elements in cs
            for (int i = 0; i < cs.length; ++i) {
                Object e = cs[i];
                //先檢查e是否在elements中
                if (indexOf(e, elements, 0, len) < 0 &&
                //再檢查e是否在待添加元素set中
                    indexOf(e, cs, 0, added) < 0)
                    //cs被遍歷過的範圍都可以作爲待添加set使用
                    cs[added++] = e;
            }
            if (added > 0) {//如果需要添加元素
                Object[] newElements = Arrays.copyOf(elements, len + added);
                System.arraycopy(cs, 0, newElements, len, added);
                setArray(newElements);
            }
            return added;
        } finally {
            lock.unlock();
        }
    }

The returned array will be “safe” in that no references to it are maintained by this collection. (In other words, this method must allocate a new array even if this collection is backed by an array). The caller is thus free to modify the returned array.

首先要知道Collection#toArray這個方法是保證了返回一個集合的數組副本(見上面註釋),也就是說我們可以隨意操作cs這個數組,而不用擔心對參數c造成影響。
在這裏插入圖片描述
函數的主要邏輯如上圖所示,indexOf(e, elements, 0, len)用來檢查遍歷元素e是否在elements中,indexOf(e, cs, 0, added)用來檢查e是否在待添加元素set中。這樣檢查是爲了cs中有重複的元素。

注意,實際上沒有生成新的set作爲待添加元素set,而是直接重複利用cs數組,因爲每遍歷過了當前元素後,這個元素的位置就可以隨意佔用了。

最後待添加元素set的範圍就是cs[0, added-1],因爲added是添加的元素個數。該函數返回的也是added。

總結

雖然CopyOnWriteArraySet的底層實現完全依賴了CopyOnWriteArrayList,但它還是保證了元素的唯一性。

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