CopyOnWriteArrayList的增删改查实现原理
https://www.cnblogs.com/simple-focus/p/7439919.html
篇文章的目的如下:
- 了解一下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至数组最后一个元素往前移动一个格
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支持读多写少的并发情况。
CopyOnWriteArrayList的增删改查实现原理的更多相关文章
- elasticsearch 增删改查底层原理
elasticsearch专栏:https://www.cnblogs.com/hello-shf/category/1550315.html 一.预备知识 在对document的curd进行深度分析 ...
- Mybatis实现简单的CRUD(增删改查)原理及实例分析
Mybatis实现简单的CRUD(增删改查) 用到的数据库: CREATE DATABASE `mybatis`; USE `mybatis`; DROP TABLE IF EXISTS `user` ...
- 使用JDBC分别利用Statement和PreparedStatement来对MySQL数据库进行简单的增删改查以及SQL注入的原理
一.MySQL数据库的下载及安装 https://www.mysql.com/ 点击DOWNLOADS,拉到页面底部,找到MySQL Community(GPL)Downloads,点击 选择下图中的 ...
- StringBuffer类(增删改查及长度可变原理)
1 package cn.itcast.p2.stringbuffer.demo; 2 3 public class StringBufferDemo { 4 5 public static void ...
- JS组件系列——又一款MVVM组件:Vue(一:30分钟搞定前端增删改查)
前言:关于Vue框架,好几个月之前就听说过,了解一项新技术之后,总是处于观望状态,一直在犹豫要不要系统学习下.正好最近有点空,就去官网了解了下,看上去还不错的一个组件,就抽空研究了下.最近园子里vue ...
- JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(二)
前言:上篇 JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(一) 介绍了下knockout.js的一些基础用法,由于篇幅的关系,所以只能分成两篇,望见谅!昨天就 ...
- YII2生成增删改查
下载完成后在basic/db.php配置数据库参数. 1.配置虚拟主机后进入YII入口文件 index.php 进行get传值 ?r=gii ,进入创建界面 2.点击 Model Generator下 ...
- 最简单的jsp+servlet的增删改查代码
package ceet.ac.cn.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.s ...
- MyBatis学习--简单的增删改查
jdbc程序 在学习MyBatis的时候先简单了解下JDBC编程的方式,我们以一个简单的查询为例,使用JDBC编程,如下: Public static void main(String[] args) ...
随机推荐
- 设置 PyCharm 软件中 Terminal 窗口 中启动的 python 交互命令的版本
设置 PyCharm 软件中 Terminal 窗口 中启动的 python 交互命令的版本 python2 和 python3 有很大的不同,使用python2 编写的程序,如果使用python3 ...
- Java-马士兵设计模式学习笔记-工厂模式-用Jdom模拟Spring
一.概述 1.目标:模拟Spring的Ioc 2.用到的知识点:利用jdom的xpath读取xml文件,反射 二.有如下文件: 1.applicationContext.xml <?xml ve ...
- Win32编程中如何处理控制台消息
这篇文章讨论如何处理所有的控制台消息. 第一步,首先要安装一个事件钩子,也就是说要建立一个回调函数.调用Win32 API,原型如下: BOOL SetConsoleCtrlHandler(PHAND ...
- JavaScript中的Array.prototype.slice.call()方法学习
JavaScript中的Array.prototype.slice.call(arguments)能将有length属性的对象转换为数组(特别注意: 这个对象一定要有length属性). 但有一个例外 ...
- 监控linux系统的简易脚本
我先把脚本粘贴在这吧,方便大家观看,其中也是借鉴了不少其他大神的东西,这个脚本主要是用来监控服务器.用户.日志,还得创建备份,等等等等.最近学的shell比较多,就用这个来练练手了,比较简单,大家凑合 ...
- 《Head First Servlets & JSP》-12-Web应用安全
serlvet安全的4大要素 认证.授权.机密性和数据完整性. 容器完成认证和授权的过程 代码中不要有安全信息 大多数Web应用,大多数情况下Web应用的安全约束都应该以声明方式处理,即在部署描述文档 ...
- 前端文件加载 net::ERR_CONTENT_LENGTH_MISMATCH
前端文章加载的时候有的时候图片不显示,有的时候文件加载不了,检查nginx设置都没有问题,最近才不显示,经检查是nginx服务器磁盘空间已满,将.log文件移动到其他位置 cp /dev/null ...
- Powershell Deploy Service Fabric Application To Local Cluster
之前写过一篇用 Powershell 部署 Service Fabric Application 到本地集群的随笔,感觉过程有点复杂,这次将流程简化,只需要将应用程序打包,加上配置文件就可以了. ...
- 动态变更Repeater控件HeaderTemplate列名
本博文,Insus.NET教你动态实现变更Repeater控件HeaderTemplate列名.一般情况之下,是不需要动态变更,只有动态有Repeater控件不变情况之下,来显示多种数据源进行绑定.这 ...
- Wannafly挑战赛28B(DP,思维,字符串)
#include<bits/stdc++.h>using namespace std;int n;int nxt[3][100007];char buff[100007];const ch ...