寫時復制原理(copy-on-write)


CopyOnWrite特點

  • 一般用於讀多寫少的情況,用於提高讀取數據的效率
  • 注意的是,讀數據並不能保證實時性,因為讀取時,讀取的是舊數組的數據
  • 缺點是:占用內存(每添加一次就需要復制一個數據)和數據一致性問題(不能保證實時數據)

以CopyOnWriteArrayList源碼進行分析

屬性

// 顯式操作的重入鎖對象
final transient ReentrantLock lock = new ReentrantLock();
// 用來存儲元素的對象數組
private transient volatile Object[] array;

構造器

// 默認構造器
public CopyOnWriteArrayList() {
    setArray(new Object[0]); // 數組長度為0
}

// 給定一個Collection容器來構建CopyOnWriteArrayList實例
public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    if (c.getClass() == CopyOnWriteArrayList.class)
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else {
        elements = c.toArray();
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    setArray(elements);
}

// 給定一個數組來構建CopyOnWriteArrayList實例
public CopyOnWriteArrayList(E[] toCopyIn) {
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

寫方法【寫時復制】

add(E e) - 添加一個元素

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 上鎖
    lock.lock();
    try {
        // 獲取對象數組
        Object[] elements = getArray(); // =========== 舊對象數組
        int len = elements.length;
        // 復制原來的對象數組,並返回一個比源對象數組長度 +1 的對象數組 
        Object[] newElements = Arrays.copyOf(elements, len + 1); // =========== 新對象數組
        // 將元素添加到對象數組的末尾
        newElements[len] = e;
        // 替換內部對象數組的引用
        setArray(newElements);
        return true;
    } finally {
        // 解鎖
        lock.unlock();
    }
}

add(int index, E 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]; // =========== 新對象數組
            // 將elements下標為0開始的元素復制到newElements下標為[0,index)處
            System.arraycopy(elements, 0, newElements, 0, index);
            // 將elements下標為index開始的元素復制到newElements下標為[index+1,numMoved)處
            System.arraycopy(elements, index, newElements, index + 1,numMoved);
        }
        // 將element插入到newElements的index處
        newElements[index] = element;
        // 替換內部對象數組的引用
        setArray(newElements);
    } finally {
        lock.unlock();
    }
}

set(int index, E element) - 替換指定位置的元素

public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray(); // =========== 舊對象數組
        // index位置的舊元素
        E oldValue = get(elements, index);

        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // 如果舊元素和新元素相等則直接返回就得對象數組不進行插入操作
            // Not quite a no-op; ensures volatile write semantics
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

讀方法

final Object[] getArray() {
    return array; // 獲取內部對象數組
}

// 根據下標獲取元素
public E get(int index) {
    return get(getArray(), index);
}

@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
    return (E) a[index];
}

使用測試

public class Main {

	public static void main(String[] args) {

		CopyOnWriteArrayList<String> cow = new CopyOnWriteArrayList<String>();
		cow.add("a1");
		cow.add("a2");
		cow.add(1, "a1.5");
		System.out.println("=====" + cow + "=====");
		
		cow.set(1, "hehe");
		System.out.println("=====" + cow + "=====");
		
		/**
		 * output:
				=====[a1, a1.5, a2]=====
				=====[a1, hehe, a2]=====
		 */
		
	}
}


免責聲明!

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



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