这篇文章的目的如下:

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

1. List接口

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

  1. public interface List<E> extends Collection<E> {
  2.  
  3. int size();
  4. boolean isEmpty();
  5. boolean contains(Object o);
  6. Iterator<E> iterator();
  7. Object[] toArray();
  8. <T> T[] toArray(T[] a);
  9. boolean add(E e);
  10. boolean remove(Object o);
  11. boolean containsAll(Collection<?> c);
  12. boolean addAll(Collection<? extends E> c);
  13. boolean addAll(int index, Collection<? extends E> c);
  14. boolean removeAll(Collection<?> c);
  15. boolean retainAll(Collection<?> c);
  16. void clear();
  17. boolean equals(Object o);
  18. int hashCode();
  19.  
  20. E get(int index);
  21. E set(int index, E element);
  22. void add(int index, E element);
  23. E remove(int index);
  24. int indexOf(Object o);
  25. int lastIndexOf(Object o);
  26. ListIterator<E> listIterator();
  27. ListIterator<E> listIterator(int index);
  28. List<E> subList(int fromIndex, int toIndex);
  29. }

2 ArrayList

2.1 几个重点

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

2.2 增删改查

1)增

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

2)删

  1. public E remove(int index) {
  2. //判断是否越界
  3. rangeCheck(index);
  4.  
  5. modCount++;
  6. E oldValue = elementData(index);
  7.  
  8. int numMoved = size - index - 1;
  9. //若该元素不是最后一个元素的话,将index+1至数组最后一个元素整体向前移动一格
  10. if (numMoved > 0)
  11. System.arraycopy(elementData, index+1, elementData, index,
  12. numMoved);
  13. elementData[--size] = null; // clear to let GC do its work
  14.  
  15. return oldValue;
  16. }
  17. 3)改
  18.  
  19. public E set(int index, E element) {
  20. rangeCheck(index);
  21.  
  22. E oldValue = elementData(index);
  23. elementData[index] = element;
  24. return oldValue;
  25. }

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

4)查

  1. public E get(int index) {
  2. rangeCheck(index);
  3.  
  4. return elementData(index);
  5. }
  6.  
  7. 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)增

  1. public boolean add(E e) {
  2. final ReentrantLock lock = this.lock;
  3. //获得锁
  4. lock.lock();
  5. try {
  6. Object[] elements = getArray();
  7. int len = elements.length;
  8. //复制一个新的数组
  9. Object[] newElements = Arrays.copyOf(elements, len + 1);
  10. //插入新值
  11. newElements[len] = e;
  12. //将新的数组指向原来的引用
  13. setArray(newElements);
  14. return true;
  15. } finally {
  16. //释放锁
  17. lock.unlock();
  18. }
  19. }
  20.  
  21. public void add(int index, E element) {
  22. final ReentrantLock lock = this.lock;
  23. lock.lock();
  24. try {
  25. Object[] elements = getArray();
  26. int len = elements.length;
  27. if (index > len || index < 0)
  28. throw new IndexOutOfBoundsException("Index: "+index+
  29. ", Size: "+len);
  30. Object[] newElements;
  31. int numMoved = len - index;
  32. if (numMoved == 0)
  33. newElements = Arrays.copyOf(elements, len + 1);
  34. else {
  35. newElements = new Object[len + 1];
  36. System.arraycopy(elements, 0, newElements, 0, index);
  37. System.arraycopy(elements, index, newElements, index + 1,
  38. numMoved);
  39. }
  40. newElements[index] = element;
  41. setArray(newElements);
  42. } finally {
  43. lock.unlock();
  44. }
  45. }

2)删

  1. public E remove(int index) {
  2. final ReentrantLock lock = this.lock;
  3. //获得锁
  4. lock.lock();
  5. try {
  6. Object[] elements = getArray();
  7. int len = elements.length;
  8. E oldValue = get(elements, index);
  9. int numMoved = len - index - 1;
  10. if (numMoved == 0)
  11. //如果删除的元素是最后一个,直接复制该元素前的所有元素到新的数组
  12. setArray(Arrays.copyOf(elements, len - 1));
  13. else {
  14. //创建新的数组
  15. Object[] newElements = new Object[len - 1];
  16. //将index+1至最后一个元素向前移动一格
  17. System.arraycopy(elements, 0, newElements, 0, index);
  18. System.arraycopy(elements, index + 1, newElements, index,
  19. numMoved);
  20. setArray(newElements);
  21. }
  22. return oldValue;
  23. } finally {
  24. lock.unlock();
  25. }
  26. }

3)改

  1. public E set(int index, E element) {
  2. final ReentrantLock lock = this.lock;
  3. //获得锁
  4. lock.lock();
  5. try {
  6. Object[] elements = getArray();
  7. E oldValue = get(elements, index);
  8.  
  9. if (oldValue != element) {
  10. int len = elements.length;
  11. //创建新数组
  12. Object[] newElements = Arrays.copyOf(elements, len);
  13. //替换元素
  14. newElements[index] = element;
  15. //将新数组指向原来的引用
  16. setArray(newElements);
  17. } else {
  18. // Not quite a no-op; ensures volatile write semantics
  19. setArray(elements);
  20. }
  21. return oldValue;
  22. } finally {
  23. //释放锁
  24. lock.unlock();
  25. }
  26. }

4)查

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

3.3 总结

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

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

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

转自:https://www.cnblogs.com/simple-focus/p/7439919.html

ArrayList和CopyOnWriteArrayList(转载)的更多相关文章

  1. ArrayList和CopyOnWriteArrayList

    这篇文章的目的如下: 了解一下ArrayList的增删改查实现原理 看看为什么说ArrayList查询快而增删慢? CopyOnWriteArrayList为什么并发安全且性能比Vector好 1. ...

  2. ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)

    本篇文章主要是学习后的知识记录,存在不足,或许不够深入,还请谅解. 目录 ArrayList源码解析 ArrayList中的变量 ArrayList构造函数 ArrayList中的add方法 Arra ...

  3. 并发容器之CopyOnWriteArrayList(转载)

    Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改, ...

  4. [转载] fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)

    说明: 转载自http://www.cnblogs.com/skywang12345/p/3308762.html概要 前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对 ...

  5. Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)

    概要 前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解.内容包括::1 fail-fast简介2 fail-fast示例 ...

  6. Java多线程系列--“JUC集合”02之 CopyOnWriteArrayList

    概要 本章是"JUC系列"的CopyOnWriteArrayList篇.接下来,会先对CopyOnWriteArrayList进行基本介绍,然后再说明它的原理,接着通过代码去分析, ...

  7. 【转】Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)

    概要 前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解.内容包括::1 fail-fast简介2 fail-fast示例 ...

  8. 由浅入深——从ArrayList浅谈并发容器

    原创作品转载请附:https://www.cnblogs.com/superlsj/p/11655523.html 一.一个案例引发的思考 public class ArrayListTest { p ...

  9. Java集合--ArrayList出现同步问题的原因

    1 fail-fast简介 fail-fast 机制是java集合(Collection)中的一种错误机制.当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件.例如:当某一个线 ...

随机推荐

  1. osg qt fbx

    void TeslaManage::loadModelFile(QString &filename) { file_node = osgDB::readNodeFile(std::string ...

  2. 阶段5 3.微服务项目【学成在线】_day16 Spring Security Oauth2_18-认证接口开发-接口开发-service

    定义AuthController 实现刚才写的api接口 controller定义热requestMapping 是 / 就可以了. 因为我们的登陆跟路径就是/auth. 这样到login就是 /au ...

  3. Centos7安装php5.6并配置php-fpm协同工作

    yum install epel-release rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm yum in ...

  4. Python 爬虫从入门到进阶之路

    https://www.cnblogs.com/weijiutao/p/10735455.html

  5. PHP和MySQL实现的简单Demo

    实现对输入的数据进行保存数据库的操作: index.html: <html lang="en"> <head> <meta charset=" ...

  6. iOS面试题超全!

    之前看了很多面试题,感觉要不是不够就是过于冗余,于是我将网上的一些面试题进行了删减和重排,现在分享给大家.(题目来源于网络,侵删) 1. Object-c的类可以多重继承么?可以实现多个接口么?Cat ...

  7. iOS-UINavigationController多控制器管理

    UINavigationController 7.8.1 添加子控制器进栈 UINavigationController *nav = [[UINavigationController alloc]  ...

  8. 2019年11月27日 Linux所学知识 总结

    查看网络信息和网络状态 nmcli connection show 使用con-name参数指定公司使用的网络会话名称company,然后依次用ifname参数指定本机的网卡名称. 用autoconn ...

  9. 在vue中使用axios发送post请求,参数方式

    由于后台接收的参数格式为FormData格式, 在axios中参数格式默认为, 在传参数前,将原先官方提供的格式 改为如下: axios({ url: '../../../room/listRoomP ...

  10. 25.Spark下载源码和安装和使用

    安装scala 上传安装包 解压 配置scala相关的环境变量 export SCALA_HOME=/opt/modules/scala-2.11.4 export PATH=$PATH:$SCALA ...