博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ArrayList 和 CopyOnWriteArrayList
阅读量:6672 次
发布时间:2019-06-25

本文共 6149 字,大约阅读时间需要 20 分钟。

hot3.png

这篇文章的目的如下:

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

1. List接口

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

public interface List
extends Collection
{ int size(); boolean isEmpty(); boolean contains(Object o); Iterator
iterator(); Object[] toArray();
T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection
c); boolean addAll(Collection
c); boolean addAll(int index, Collection
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
listIterator(); ListIterator
listIterator(int index); List
subList(int fromIndex, int toIndex);}

2 ArrayList

2.1 几个重点

  • 底层是数组,初始大小为10
  • 插入时会判断数组容量是否足够,不够的话会进行扩容
  • 所谓扩容就是新建一个新的数组,然后将老的数据里面的元素复制到新的数组里面
  • 移除元素的时候也涉及到数组中元素的移动,删除指定index位置的元素,然后将index+1至数组最后一个元素往前移动一个格

2.2 增删改查

1)增

public boolean add(E e) {    //进行数组容量判断,不够就扩容    ensureCapacityInternal(size + 1);  // Increments modCount!!    elementData[size++] = e;    return true; } public void add(int index, E element) {    //检查是否会越界    rangeCheckForAdd(index);    //进行数组容量判断,不够就扩容    ensureCapacityInternal(size + 1);  // Increments modCount!!    //将index至数据最后一个元素整体往后移动一格,然后插入新的元素    System.arraycopy(elementData, index, elementData, index + 1,                    size - index);    elementData[index] = element;    size++;}

2)删

public E remove(int index) {    //判断是否越界    rangeCheck(index);    modCount++;    E oldValue = elementData(index);    int numMoved = size - index - 1;    //若该元素不是最后一个元素的话,将index+1至数组最后一个元素整体向前移动一格    if (numMoved > 0)        System.arraycopy(elementData, index+1, elementData, index,                         numMoved);    elementData[--size] = null; // clear to let GC do its work    return oldValue;    }

3)改

public E set(int index, E element) {    rangeCheck(index);    E oldValue = elementData(index);    elementData[index] = element;    return oldValue;}

逻辑很简单,将数组对应index的元素进行替换

4)查

public E get(int index) {    rangeCheck(index);    return elementData(index);}E elementData(int index) {return (E) elementData[index]; }

逻辑很简单,进行数组越界判断,获取数组对应index的元素

2.3 总结

以上部分就是ArrayList的增删改查原理,以此也可以解答我们第二个问题了,ArrayList的底层是数组,所以查询的时候直接根据索引可以很快找到对应的元素,改也是如此,找到index对应元素进行替换。而增加和删除就涉及到数组元素的移动,所以会比较慢。

3 CopyOnWriteArrayList

3.1 几个要点

  • 实现了List接口
  • 内部持有一个ReentrantLock lock = new ReentrantLock();
  • 底层是用volatile transient声明的数组 array
  • 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array

3.2 增删改查

1)增

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();    }}   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();    }}

2)删

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];            //将index+1至最后一个元素向前移动一格            System.arraycopy(elements, 0, newElements, 0, index);            System.arraycopy(elements, index + 1, newElements, index,                             numMoved);            setArray(newElements);        }        return oldValue;    } finally {        lock.unlock();    }}

3)改

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 {            // Not quite a no-op; ensures volatile write semantics            setArray(elements);        }        return oldValue;    } finally {        //释放锁        lock.unlock();    }}

4)查

//直接获取index对应的元素public E get(int index) {return get(getArray(), index);}private E get(Object[] a, int index) {return (E) a[index];}

3.3 总结

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

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

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

转载于:https://my.oschina.net/u/2935389/blog/3031099

你可能感兴趣的文章
-bash: zip: command not found提示解决办法
查看>>
机器人市场机遇和挑战并存
查看>>
来看一场 AI 重建的 3D 全息世界杯比赛!
查看>>
为什么使用TypeReference
查看>>
Promise Race, 并不公平的 Race
查看>>
动态权限<三>华为小米特殊机制
查看>>
linux基本命令学习01
查看>>
Freebsd for ECS 系统盘扩容示例
查看>>
IPad分屏,当电脑第二显示屏
查看>>
kprobe原理解析
查看>>
String的线程安全
查看>>
云服务提供商告诉您云服务器对营销型网站的重要性
查看>>
前端通信:ajax设计方案(七)--- 增加请求错误监控、前端负载均衡以、请求宕机切换以及迭代问题修复...
查看>>
软硬件一体提高主链性能,「HPB芯链」想构建区块链版的云计算
查看>>
python中用string.maketrans和translate巧妙替换字符串
查看>>
全面深入认识C变量
查看>>
C语言嵌入式系统编程修炼之道——内存操作篇 原创21cnbao2005-10-19 22:06:00评论(0)...
查看>>
深思熟虑后做出的决定
查看>>
MYSQL中TIMESTAMP类型的默认值
查看>>
用C#动态创建Access数据库
查看>>