探索CopyOnWriteArrayList底層實現
阿新 • • 發佈:2020-12-22
簡單介紹
由於CopyOnWriteArrayList
的註釋
並不是很多,所以在這裡簡單的說明下,它屬於執行緒安全,底層是通過生成陣列的新副本
來實現的,也就是在修改列表元素/結構的情況會生成新副本。簡單地說,它是ArrayList的一個變體!探索CopyOnWriteArrayList
原始碼是基於JDK1.8
版本的。
開幹
開始進入到看原始碼的時間吧!
資料結構
//支援隨機訪問,可克隆,可序列化 public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { //序列號 private static final long serialVersionUID = 8673264195747942595L; //目前先知道是鎖就行了,後續會有新文章來進行詳細說明 //保證同一個時間內只有一個執行緒能訪問, final transient ReentrantLock lock = new ReentrantLock(); //保證變數的可見性,但無法保證原子性 //至於什麼是可見性、原子性,較為難理解,況且也不是本章的重點,加上作者對其的理解還不夠,所以後續才會出文章去做詳細說明 //該陣列用於存放元素 private transient volatile Object[] array; //相比於ArrayList,為什麼沒有了size、modCount成員屬性呢? //因為每次新增/刪除元素時,都會生成陣列的新副本,也就是說新副本代替了size的作用 //modCount在ArrayList中主要用於在迭代器的結構修改判斷中,而CopyOnWriteArrayList的迭代器中不支援結構修改,為什麼不支援呢? //原始碼中並未提到為什麼不支援,以下主要是自我的理解 //結構修改中必然涉及到加鎖,若對迭代器加鎖了,要是對它進行遍歷上千條資料,那其他執行緒就不用執行了,所以迭代器萬不可加鎖! //不能加鎖,那對於結構的修改勢必會造成併發訪問的問題,所以目前是沒有提供支援,純屬個人理解! //JDK1.8並未有該類的相關注釋,偏向底層,目前作者只知道它是跟鎖有關聯的,若想知道可自行百度 private static final sun.misc.Unsafe UNSAFE; private static final long lockOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = CopyOnWriteArrayList.class; lockOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("lock")); } catch (Exception e) { throw new Error(e); } } }
建構函式
/** * 建立空陣列 */ public CopyOnWriteArrayList() { setArray(new Object[0]); } /** * 構建一個包含指定collection集合的物件,CopyOnWriteArrayList容量大小和該集合大小一致,指定集合中的元素按照迭代器的順序排列 * collection集合型別有Map、set、List等子類,所以入參可以是多種型別 * CopyOnWriteArrayList保證陣列中元素型別是Object * @param c 指定集合 */ public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); //c.toArray 可能不會返回正確的Object[]型別,這邊可能會利用多型的性質,如 A a = new B() if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); } /** * 構建一個包含指定陣列的物件 * 生成陣列的新副本,並讓該物件中的陣列指向它 * @param toCopyIn 指定陣列 */ public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }
方法說明
接下來按照類的宣告順序介紹方法
,有必要的情況下結合例子進行說明。
簡單方法
/** * 獲取陣列 * 之所以沒有加上private訪問修飾符,是因為在CopyOnWriteArraySet類中使用了該方法 * 加上final防止繼承類去覆寫該方法 * @return 陣列 */ final Object[] getArray() { return array; } /** * 設定陣列 * @param a 新陣列 */ final void setArray(Object[] a) { array = a; } /** * 獲取陣列元素的個數 * @return 陣列元素的個數 */ public int size() { return getArray().length; } /** * 判斷陣列是否為空 * @return 陣列是否為空 */ public boolean isEmpty() { return size() == 0; } /** * 判斷兩個物件是否相等 * @param o1 物件 * @param o2 物件 * @return 兩個物件是否相等 */ private static boolean eq(Object o1, Object o2) { return (o1 == null) ? o2 == null : o1.equals(o2); } /** * 正向遍歷,獲取從指定起始索引到指定結束索引之間搜尋指定元素的索引 * 若指定區間不存在指定元素的話則返回-1 * @param o 物件 * @param elements 陣列 * @param index 指定起始索引 * @param fence 指定結束索引 * @return 指定元素的索引 */ private static int indexOf(Object o, Object[] elements, int index, int fence) { if (o == null) { for (int i = index; i < fence; i++) if (elements[i] == null) return i; } else { for (int i = index; i < fence; i++) if (o.equals(elements[i])) return i; } return -1; } /** * 反向遍歷,獲取從指定起始索引到索引為0之間搜尋指定元素的索引 * @param o 物件 * @param elements 陣列 * @param index 指定起始索引 * @return 指定元素的索引 */ private static int lastIndexOf(Object o, Object[] elements, int index) { if (o == null) { for (int i = index; i >= 0; i--) if (elements[i] == null) return i; } else { for (int i = index; i >= 0; i--) if (o.equals(elements[i])) return i; } return -1; } /** * 判斷陣列中是否包含指定元素 * @param o 指定元素 * @return 陣列是否包含指定元素 */ public boolean contains(Object o) { Object[] elements = getArray(); return indexOf(o, elements, 0, elements.length) >= 0; } /** * 正向遍歷,獲取指定元素的索引 * 若未發現指定元素則返回-1 * @param o 指定元素 * @return 指定元素的索引 */ public int indexOf(Object o) { Object[] elements = getArray(); return indexOf(o, elements, 0, elements.length); } /** * 正向遍歷,獲取從指定起始索引處開始搜尋指定元素的索引 * 若未發現指定元素則返回-1 * @param e 指定元素 * @param index 指定起始索引 * @return 指定元素的索引 */ public int indexOf(E e, int index) { Object[] elements = getArray(); return indexOf(e, elements, index, elements.length); } /** * 反向遍歷,獲取指定元素的索引 * @param o 指定元素 * @return 指定元素的索引 */ public int lastIndexOf(Object o) { Object[] elements = getArray(); return lastIndexOf(o, elements, elements.length - 1); } /** * 反向遍歷,獲取從指定起始索引處開始搜尋指定元素的索引 * @param e 指定元素 * @param index 指定起始索引 * @return 指定元素的索引 */ public int lastIndexOf(E e, int index) { Object[] elements = getArray(); return lastIndexOf(e, elements, index); } /** * 拷貝新的CopyOnWriteArrayList物件,沒有拷貝物件中的陣列,屬於淺拷貝 * @return 物件 */ public Object clone() { try { @SuppressWarnings("unchecked") CopyOnWriteArrayList<E> clone = (CopyOnWriteArrayList<E>) super.clone(); clone.resetLock(); return clone; } catch (CloneNotSupportedException e) { throw new InternalError(); } } /** * 返回一個包含所有列表元素的有序(按照新增順序)陣列 * 此方法是建立一個新陣列,方便使用者能夠隨便操作新陣列 * @return 新陣列 */ public Object[] toArray() { Object[] elements = getArray(); return Arrays.copyOf(elements, elements.length); } /** * 將列表的所有元素放入到指定陣列中並返回 * * 注意:T型別要麼是陣列中資料的相同型別,要麼是陣列中資料的父型別,運用多型性質 * 若傳入的新陣列容量 < 列表容量,則取它的類型別來建立一個包含列表元素的新陣列,並返回 * 若傳入的新陣列容量 > 列表容量,則將列表中的元素按照順序拷貝到新陣列中,同時將新陣列中索引為size的值設定成null * * 一開始我也好奇為啥要在索引為size上設定個null呢? * 看了註釋加上自我的理解,若傳入的新陣列是個空陣列的話,那麼除了拷貝列表元素後剩餘的所有空間的值都為null,此時在給索引為size的值設定成null似乎沒有多大 * 意思;另外一種情況是若傳入的新陣列不是個空陣列,那這個設定就有意義了,傳入的新陣列的某些元素會被列表元素覆蓋,同時有個null,剩下的才是自己本身的資料,呈現這樣子一種效果 * * List<Integer> list = new ArrayList<>(); * list.add(11); * * Integer[] str = new Integer[]{1,2,3,4,5,6,7,8,9,10}; * Integer[] s1 = list.toArray(str); * * for (Integer s : s1) { * System.out.println(s + ","); * } * * 輸出結果:11,null,3,4,5,6,7,8,9,10, * 那麼設定這個null的意義就在於能夠確定列表中元素個數(長度),但有個前提就是呼叫者知道連結串列中的所有節點資訊不存在null才有意義,目前我只有想到這一種情況下有用! * * @param a 指定陣列 * @return 填充完列表元素的指定陣列 */ public <T> T[] toArray(T a[]) { Object[] elements = getArray(); int len = elements.length; if (a.length < len) return (T[]) Arrays.copyOf(elements, len, a.getClass()); else { System.arraycopy(elements, 0, a, 0, len); if (a.length > len) a[len] = null; return a; } } /** * 判斷陣列中是否包含指定集合中的所有元素 * 集合中的元素但凡在陣列中未包含則返回false * @param c 指定集合 * @return 陣列中是否包含指定集合中的所有元素 */ public boolean containsAll(Collection<?> c) { Object[] elements = getArray(); int len = elements.length; for (Object e : c) { if (indexOf(e, elements, 0, len) < 0) return false; } return true; } /** * 集合與陣列取交集 * 最終陣列中只包含與集合共有的元素,相當於在修改陣列 * @param c 指定集合 * @return 陣列元素是否被修改成功 */ public boolean retainAll(Collection<?> c) { if (c == null) throw new NullPointerException(); final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (len != 0) { // temp array holds those elements we know we want to keep int newlen = 0; Object[] temp = new Object[len]; for (int i = 0; i < len; ++i) { Object element = elements[i]; if (c.contains(element)) temp[newlen++] = element; } if (newlen != len) { setArray(Arrays.copyOf(temp, newlen)); return true; } } return false; } finally { lock.unlock(); } } /** * 清空陣列中的元素 */ public void clear() { final ReentrantLock lock = this.lock; lock.lock(); try { setArray(new Object[0]); } finally { lock.unlock(); } } /** * 遍歷陣列,並對陣列中的元素進行指定處理 * 讀取時不會發生衝突,因為新增、刪除、替換等操作都是使用新副本,只不過會出現實時資料不一致,但最終是一致的 * @param action 函式式介面,對陣列中的元素指定處理 */ public void forEach(Consumer<? super E> action) { if (action == null) throw new NullPointerException(); Object[] elements = getArray(); int len = elements.length; for (int i = 0; i < len; ++i) { @SuppressWarnings("unchecked") E e = (E) elements[i]; action.accept(e); } } /** * 根據指定條件移除元素 * @param filter 使用指定條件來過濾元素 * @return 是否移除成功 */ public boolean removeIf(Predicate<? super E> filter) { if (filter == null) throw new NullPointerException(); final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (len != 0) { int newlen = 0; Object[] temp = new Object[len]; for (int i = 0; i < len; ++i) { @SuppressWarnings("unchecked") E e = (E) elements[i]; if (!filter.test(e)) temp[newlen++] = e; } if (newlen != len) { setArray(Arrays.copyOf(temp, newlen)); return true; } } return false; } finally { lock.unlock(); } } /** * 根據指定規則替換所有舊元素 * operator.apply方法:舊元素作為入參傳入,根據規則返回新元素,然後進行替換 * @param operator 指定規則,函式式介面 */ public void replaceAll(UnaryOperator<E> operator) { if (operator == null) throw new NullPointerException(); final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); for (int i = 0; i < len; ++i) { @SuppressWarnings("unchecked") E e = (E) elements[i]; newElements[i] = operator.apply(e); } setArray(newElements); } finally { lock.unlock(); } } /** * 根據指定規則對陣列中的元素進行排序 * 若沒有指定規則則使用預設的升序進行排序 * 指定規則後會呼叫自定義比較器中的compare方法進行比較排序 * @param c 自定義比較器,覆寫compare方法 */ public void sort(Comparator<? super E> c) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); Object[] newElements = Arrays.copyOf(elements, elements.length); @SuppressWarnings("unchecked") E[] es = (E[])newElements; Arrays.sort(es, c); setArray(newElements); } finally { lock.unlock(); } } /** * 自定義序列化 * 寫入陣列的長度及陣列的元素方便構建 * @param s 輸出流 */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); Object[] elements = getArray(); // Write out array length s.writeInt(elements.length); // Write out all elements in the proper order. for (Object element : elements) s.writeObject(element); } /** * 自定義反序列化 * @param s 輸入流 */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); //生成新的鎖物件 resetLock(); int len = s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, len); Object[] elements = new Object[len]; for (int i = 0; i < len; i++) elements[i] = s.readObject(); setArray(elements); } /** * 獲取陣列元素的字串 * @return 陣列元素的字串 */ public String toString() { return Arrays.toString(getArray()); } /** * 先判斷當前物件與指定物件是否指向同一個物件,就是在判斷地址 * 緊接著判斷指定物件屬於List的子類 * 緊接著獲取該物件的迭代器 * 若兩個迭代器的元素個數不相等,則返回false * 若兩個迭代器的元素個數相等,則將兩個迭代器的元素進行對應的比較,但凡出現對應的元素不相等則返回false * @param o 指定物件 * @return 當前物件與指定物件是否相等 */ public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof List)) return false; List<?> list = (List<?>)(o); Iterator<?> it = list.iterator(); Object[] elements = getArray(); int len = elements.length; for (int i = 0; i < len; ++i) if (!it.hasNext() || !eq(elements[i], it.next())) return false; if (it.hasNext()) return false; return true; } /** * 獲取雜湊值 * @return 雜湊值 */ public int hashCode() { int hashCode = 1; Object[] elements = getArray(); int len = elements.length; for (int i = 0; i < len; ++i) { Object obj = elements[i]; hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode()); } return hashCode; } /** * 獲取分割迭代器 * 由於該方法涉及到另外一個介面,會另外新起一篇文章來講解該內容,這裡就不做闡述 * 附上文章地址:http://zlia.tech/2019/08/28/explain-arraylist-spliterator-sourcecode * @return */ public Spliterator<E> spliterator() { return Spliterators.spliterator (getArray(), Spliterator.IMMUTABLE | Spliterator.ORDERED); } /** * 獲取指定起始索引到指定結束索引之間的元素,簡稱獲取指定子集 * 指定區間中的元素包括起始索引,不包括結束索引 * 若起始索引與結束索引相等,則返回空元素 * 對子集的操作,即呼叫set、add、remove等方法將會影響到整個陣列 * 但在先獲取子集後,又對整個陣列的結構進行修改,這時在遍歷子集則會導致報錯,而對於整體的非結構性修改則不會報錯,不過依然會影響到子集 * 所以在獲取子集後最好不要修改陣列的結構 * * 程式碼片段與ArrayList是類似的,可參考ArrayList文章:http://zlia.tech/2019/08/16/explain-arraylist-sourcecode * @param fromIndex 起始索引 * @param toIndex 結束索引 * @return 指定區間中的所有元素,稱為子集 */ public List<E> subList(int fromIndex, int toIndex) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (fromIndex < 0 || toIndex > len || fromIndex > toIndex) throw new IndexOutOfBoundsException(); return new COWSubList<E>(this, fromIndex, toIndex); } finally { lock.unlock(); } } /** * 重置鎖,生成新的鎖物件 */ private void resetLock() { UNSAFE.putObjectVolatile(this, lockOffset, new ReentrantLock()); }
獲取元素
/**
* 獲取指定索引的元素
* @param a 陣列
* @param index 指定索引
* @return 指定索引的元素
*/
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* 獲取指定索引的元素
* @param index 指定索引
* @return 指定索引的元素
*/
public E get(int index) {
return get(getArray(), index);
}
修改元素
/**
* 將指定索引處的元素修改成指定元素
* 在執行操作之前,先加上鎖,接著生成陣列的新副本,在新副本中替換元素,最後將陣列指向新副本並釋放鎖
* 在未釋放鎖之前,其他執行緒無法進入,這樣子就保證了執行緒安全
* 每次呼叫該方法都會造成新副本陣列的生成,導致記憶體飆升
* @param index 指定索引
* @param element 新元素
* @return 舊元素
*/
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
新增元素
/**
* 新增指定元素到列表尾部
* 在執行操作之前,先加上鎖,接著生成陣列的新副本,並擴充其容量,最後將陣列指向新副本並釋放鎖
* @param e 指定元素
* @return 是否新增成功
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
/**
* 新增指定元素到指定索引處
* 由於每次都會生成新副本,原先陣列的前index元素列表會拷貝到新副本中,再者原先資料的後index元素列表會拷貝到新副本中
* 原先陣列中的所有元素都拷貝到了新副本中,最終在新副本中的index位置為null,最後在設定指定元素即可
* @param index 指定索引
* @param element 指定元素
*/
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
/**
* 若陣列中未包含指定元素則進行新增到尾部
* 若陣列中已經存在指定元素則返回false
* @param e 指定元素
* @return 是否新增成功
*/
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
/**
* 若新副本中未包含指定元素則進行新增到尾部
* 若是新副本中已經存在指定元素則返回false
* 至始至終snapshot都是用來做與新副本進行比較的
* @param e 指定元素
* @param snapshot 陣列,有可能成為舊陣列
* @return 是否新增成功
*/
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) {
//生成了新的陣列副本
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
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();
}
}
/**
* 新增集合中未被陣列包含的元素到陣列尾部,相當於批量新增不存在的元素到尾部
* 集合中重複的元素只會被新增一次
* @param c 指定集合
* @return 新增到陣列中的元素個數
*/
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];
if (indexOf(e, elements, 0, len) < 0 &&
indexOf(e, cs, 0, added) < 0)
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();
}
}
/**
* 將指定集合新增到陣列尾部
* @param c 指定集合
* @return 是否新增成功
*/
public boolean addAll(Collection<? extends E> c) {
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
if (cs.length == 0)
return false;
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len == 0 && cs.getClass() == Object[].class)
setArray(cs);
else {
Object[] newElements = Arrays.copyOf(elements, len + cs.length);
System.arraycopy(cs, 0, newElements, len, cs.length);
setArray(newElements);
}
return true;
} finally {
lock.unlock();
}
}
/**
* 將指定集合新增到陣列的指定索引處
* @param index 指定索引
* @param c 指定集合
* @return 是否新增成功
*/
public boolean addAll(int index, Collection<? extends E> c) {
Object[] cs = c.toArray();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
if (cs.length == 0)
return false;
int numMoved = len - index;
Object[] newElements;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + cs.length);
else {
newElements = new Object[len + cs.length];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index,
newElements, index + cs.length,
numMoved);
}
System.arraycopy(cs, 0, newElements, index, cs.length);
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
移除元素
/**
* 移除陣列中指定索引處的元素
* @param index 指定索引
* @return 舊元素
*/
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
/**
* 移除陣列中的指定元素
* @param o 指定元素
* @return 是否移除成功
*/
public boolean remove(Object o) {
Object[] snapshot = getArray();
int index = indexOf(o, snapshot, 0, snapshot.length);
return (index < 0) ? false : remove(o, snapshot, index);
}
/**
* 移除陣列中的指定索引處的元素
* @param o 指定元素
* @param snapshot 陣列,有可能是舊陣列
* @param index 指定索引
* @return 是否移除成功
*/
private boolean remove(Object o, Object[] snapshot, int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) findIndex: {
//這部分程式碼是獲取新副本中指定元素的索引,也就是獲取最新的索引,看看新副本中有沒有存在等於指定元素的更小索引
int prefix = Math.min(index, len);
for (int i = 0; i < prefix; i++) {
if (current[i] != snapshot[i] && eq(o, current[i])) {
index = i;
break findIndex; //表示跳出指定程式碼塊
}
}
//若在新副本中未找到指定元素的索引則index不會被改變,此時的情況應該是:舊陣列的長度大於新副本陣列的長度,那麼最終會返回false
if (index >= len)
return false;
//判斷新副本的指定索引處的元素是否與指定元素相等,若相等則說明該位置的元素並未發生改變
if (current[index] == o)
break findIndex;
//此時的情況是:新副本陣列的長度大於舊陣列的長度,獲取新副本中指定元素的索引
index = indexOf(o, current, index, len);
if (index < 0)
return false;
}
Object[] newElements = new Object[len - 1];
System.arraycopy(current, 0, newElements, 0, index);
System.arraycopy(current, index + 1,
newElements, index,
len - index - 1);
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
/**
* 移除指定起始索引到結束索引之間的所有元素
* @param fromIndex 指定起始索引
* @param toIndex 指定結束索引
*/
void removeRange(int fromIndex, int toIndex) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
throw new IndexOutOfBoundsException();
int newlen = len - (toIndex - fromIndex);
int numMoved = len - toIndex;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, newlen));
else {
Object[] newElements = new Object[newlen];
System.arraycopy(elements, 0, newElements, 0, fromIndex);
System.arraycopy(elements, toIndex, newElements,
fromIndex, numMoved);
setArray(newElements);
}
} finally {
lock.unlock();
}
}
/**
* 陣列中移除指定集合的所有元素
* @param c 指定集合
* @return 是否移除成功
*/
public boolean removeAll(Collection<?> c) {
if (c == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len != 0) {
// temp array holds those elements we know we want to keep
int newlen = 0;
Object[] temp = new Object[len];
for (int i = 0; i < len; ++i) {
Object element = elements[i];
if (!c.contains(element))
temp[newlen++] = element;
}
if (newlen != len) {
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
迭代器
/**
* 獲取迭代器
* @return 迭代器
*/
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
/**
* 獲取迭代器
* @return 迭代器
*/
public ListIterator<E> listIterator() {
return new COWIterator<E>(getArray(), 0);
}
/**
* 獲取從指定起始索引開始的列表迭代器
* 列表迭代器中的元素是從指定索引開始到結束索引
* @param index 指定起始索引
* @return 列表迭代器
*/
public ListIterator<E> listIterator(int index) {
Object[] elements = getArray();
int len = elements.length;
if (index < 0 || index > len)
throw new IndexOutOfBoundsException("Index: "+index);
return new COWIterator<E>(elements, index);
}
/**
* 列表迭代器,正向迭代
* 可獲取上一個元素、下一個元素及索引
*/
static final class COWIterator<E> implements ListIterator<E> {
//當迭代器被建立後,只拿到當前陣列的引用,也就是說只擁有當前陣列的元素
//而隨著後面列表的add、remove、repalce都是在操作新副本,這些變動並不會反映到當前的引用,相當於是兩個引用
private final Object[] snapshot;
//下一個元素的索引
private int cursor;
/**
* 初始化引數
* @param elements 當前陣列
* @param initialCursor 下一個元素的索引
*/
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
/**
* 判斷是否有下一個元素
* @return 是否有下一個元素
*/
public boolean hasNext() {
return cursor < snapshot.length;
}
/**
* 判斷是否有前一個元素
* @return 是否有前一個元素
*/
public boolean hasPrevious() {
return cursor > 0;
}
/**
* 獲取下一個元素的值
* 若不存在則丟擲異常
* @return 下一個元素的值
*/
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
/**
* 獲取上一個元素
* 若不存在則丟擲異常
* @return 上一個元素
*/
@SuppressWarnings("unchecked")
public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
}
/**
* 獲取下一個元素的索引
* @return 下一個元素的索引
*/
public int nextIndex() {
return cursor;
}
/**
* 獲取上一個元素的索引
* @return 上一個元素的索引
*/
public int previousIndex() {
return cursor-1;
}
/**
* 不支援,為什麼不支援在資料結構那一欄中有提到
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* 不支援,為什麼不支援在資料結構那一欄中有提到
*/
public void set(E e) {
throw new UnsupportedOperationException();
}
/**
* 不支援,為什麼不支援在資料結構那一欄中有提到
*/
public void add(E e) {
throw new UnsupportedOperationException();
}
/**
* 遍歷元素,只能遍歷一次
* 與forEach的區別在於:可以遍歷多次
* @param consumer 函式式介面,宣告如何處理元素的函式
*/
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
Object[] elements = snapshot;
final int size = elements.length;
for (int i = cursor; i < size; i++) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
action.accept(e);
}
cursor = size;
}
}
總結
-
CopyOnWriteArrayList允許存放Null。
-
底層通過生成陣列的新副本實現,故記憶體佔用是個明顯的問題。
-
多執行緒情況下,可能讀取舊資料,只能保證資料的最終一致性。
-
CopyOnWriteArrayList適用於讀多寫少的場景。
-
CopyOnWriteArrayList在效能上沒有ArrayList、LinkedList好,畢竟加了鎖!
-
CopyOnWriteArrayList沒有擴容機制,每次新增節點前就拷貝源陣列到新陣列中,而新陣列與源陣列的長度差為1。
重點關注
執行緒安全
底層是通過生成陣列的新副本實現
由於每次都生成新副本,故記憶體佔用會相對更大
多執行緒情況下,可能讀取到舊資料(讀取在新增之前執行)