zoukankan      html  css  js  c++  java
  • 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支持读多写少的并发情况。

  • 相关阅读:
    Oracle 不走索引
    Oracle不等值链接
    查看统计信息是否过期
    JavaScript利用append添加元素报错
    Subversion Native Library Not Available & Incompatible JavaHL library loaded
    Oracle并行查询出错
    Oracle连接出错(一)
    Linux下Subclipse的JavaHL
    Java生成文件夹
    Java生成文件
  • 原文地址:https://www.cnblogs.com/lgjava/p/11731049.html
Copyright © 2011-2022 走看看