ArrayList和CopyOnWriteArrayList

这篇文章的目的如下:

  • 了解一下ArrayList和CopyOnWriteArrayList的增删改查实现原理
  • 看看为什么说ArrayList查询快而增删慢?
  • CopyOnWriteArrayList为什么并发安全且性能比Vector好

1. List接口

首先我们来看看List接口,因为ArrayList和CopyOnWriteArrayList都是实现了List接口,我们今天主要是研究增删改查原理,所以只看相应的方法即可。

public interface List<E> extends Collection<E> {
 
    int size();
    boolean isEmpty();
    boolean contains(Object o);
    Iterator<E> iterator();
    Object[] toArray();
    <T> T[] toArray(T[] a);
    boolean add(E e);
    boolean remove(Object o);
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
    boolean addAll(int index, Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    void clear();
    boolean equals(Object o);
    int hashCode();

    E get(int index);
    E set(int index, E element);
    void add(int index, E element);
    E remove(int index);
    int indexOf(Object o);
    int lastIndexOf(Object o);
    ListIterator<E> listIterator();
    ListIterator<E> listIterator(int index);
    List<E> subList(int fromIndex, int toIndex);
}

2 ArrayList

 

2.1 几个重点

 

  • 底层是数组,初始大小为10
  • 插入时会判断数组容量是否足够,不够的话会进行扩容
  • 所谓扩容就是新建一个新的数组,然后将老的数据里面的元素复制到新的数组里面
  • 移除元素的时候也涉及到数组中元素的移动,删除指定index位置的元素,然后将index+1至数组最后一个元素往前移动一个格
     1 1)增
     2 public boolean add(E e) {
     3     //进行数组容量判断,不够就扩容
     4     ensureCapacityInternal(size + 1);  // Increments modCount!!
     5     elementData[size++] = e;
     6     return true;
     7  }
     8 
     9  
    10 public void add(int index, E element) {
    11     //检查是否会越界
    12     rangeCheckForAdd(index);
    13     //进行数组容量判断,不够就扩容
    14     ensureCapacityInternal(size + 1);  // Increments modCount!!
    15     //将index至数据最后一个元素整体往后移动一格,然后插入新的元素
    16     System.arraycopy(elementData, index, elementData, index + 1,
    17                     size - index);
    18     elementData[index] = element;
    19     size++;
    20 }
    21 2) 删
    22 public E remove(int index) {
    23     //判断是否越界
    24     rangeCheck(index);
    25 
    26     modCount++;
    27     E oldValue = elementData(index);
    28 
    29     int numMoved = size - index - 1;
    30     //若该元素不是最后一个元素的话,将index+1至数组最后一个元素整体向前移动一格
    31     if (numMoved > 0)
    32         System.arraycopy(elementData, index+1, elementData, index,
    33                          numMoved);
    34     elementData[--size] = null; // clear to let GC do its work
    35 
    36     return oldValue;
    37     }
    38 
    39 3)改
    40 public E set(int index, E element) {
    41     rangeCheck(index);
    42 
    43     E oldValue = elementData(index);
    44     elementData[index] = element;
    45     return oldValue;
    46 }
    47 
    48 4) 插
    49 public E get(int index) {
    50     rangeCheck(index);
    51 
    52     return elementData(index);
    53 }
    54 
    55 E elementData(int index) {return (E) elementData[index]; }

     

2.2 总结

  • ArrayList的底层是数组,所以查询的时候直接根据索引可以很快找到对应的元素,改也是如此,找到index对应元素进行替换。而增加和删除就涉及到数组元素的移动,所以会比较慢

3 CopyOnWriteArrayList

3.1 几个要点

  • 实现了List接口

  • 内部持有一个ReentrantLock lock = new ReentrantLock();

  • 底层是用volatile transient声明的数组 array

  • 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array

  1 1)增
  2 
  3 public boolean add(E e) {
  4     final ReentrantLock lock = this.lock;
  5     //获得锁
  6     lock.lock();
  7     try {
  8         Object[] elements = getArray();
  9         int len = elements.length;
 10         //复制一个新的数组
 11         Object[] newElements = Arrays.copyOf(elements, len + 1);
 12         //插入新值
 13         newElements[len] = e;
 14         //将新的数组指向原来的引用
 15         setArray(newElements);
 16         return true;
 17     } finally {
 18         //释放锁
 19         lock.unlock();
 20     }
 21 }
 22 
 23    
 24 public void add(int index, E element) {
 25     final ReentrantLock lock = this.lock;
 26     lock.lock();
 27     try {
 28         Object[] elements = getArray();
 29         int len = elements.length;
 30         if (index > len || index < 0)
 31             throw new IndexOutOfBoundsException("Index: "+index+
 32                                                 ", Size: "+len);
 33         Object[] newElements;
 34         int numMoved = len - index;
 35         if (numMoved == 0)
 36             newElements = Arrays.copyOf(elements, len + 1);
 37         else {
 38             newElements = new Object[len + 1];
 39             System.arraycopy(elements, 0, newElements, 0, index);
 40             System.arraycopy(elements, index, newElements, index + 1,
 41                              numMoved);
 42         }
 43         newElements[index] = element;
 44         setArray(newElements);
 45     } finally {
 46         lock.unlock();
 47     }
 48 }
 49 2)删
 50 
 51 public E remove(int index) {
 52     final ReentrantLock lock = this.lock;
 53     //获得锁
 54     lock.lock();
 55     try {
 56         Object[] elements = getArray();
 57         int len = elements.length;
 58         E oldValue = get(elements, index);
 59         int numMoved = len - index - 1;
 60         if (numMoved == 0)
 61             //如果删除的元素是最后一个,直接复制该元素前的所有元素到新的数组
 62             setArray(Arrays.copyOf(elements, len - 1));
 63         else {
 64             //创建新的数组
 65             Object[] newElements = new Object[len - 1];
 66             //将index+1至最后一个元素向前移动一格
 67             System.arraycopy(elements, 0, newElements, 0, index);
 68             System.arraycopy(elements, index + 1, newElements, index,
 69                              numMoved);
 70             setArray(newElements);
 71         }
 72         return oldValue;
 73     } finally {
 74         lock.unlock();
 75     }
 76 }
 77 3)改
 78 
 79 public E set(int index, E element) {
 80     final ReentrantLock lock = this.lock;
 81     //获得锁
 82     lock.lock();
 83     try {
 84         Object[] elements = getArray();
 85         E oldValue = get(elements, index);
 86 
 87         if (oldValue != element) {
 88             int len = elements.length;
 89             //创建新数组
 90             Object[] newElements = Arrays.copyOf(elements, len);
 91             //替换元素
 92             newElements[index] = element;
 93             //将新数组指向原来的引用
 94             setArray(newElements);
 95         } else {
 96             // Not quite a no-op; ensures volatile write semantics
 97             setArray(elements);
 98         }
 99         return oldValue;
100     } finally {
101         //释放锁
102         lock.unlock();
103     }
104 }
105 4)查
106 
107 //直接获取index对应的元素
108 public E get(int index) {return get(getArray(), index);}
109 private E get(Object[] a, int index) {return (E) a[index];}

3.2 总结

从以上的增删改查中我们可以发现,增删改都需要获得锁,并且锁只有一把,而读操作不需要获得锁,支持并发。为什么增删改中都需要创建一个新的数组,操作完成之后再赋给原来的引用?这是为了保证get的时候都能获取到元素,如果在增删改过程直接修改原来的数组,可能会造成执行读操作获取不到数据。

4 CopyOnWriteArrayList为什么并发安全且性能比Vector好

我知道Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。

posted @ 2019-10-24 11:06  smile_lg  阅读(501)  评论(0编辑  收藏  举报