目录
6.removeRange(int fromIndex, int toIndex)方法
梗概
CopyOnWriteArrayList是 java.util.concurrent包下的一个用于应对高并发访问的线程安全的集合类。该类中的方法的调用方式与普通的集合类一致,但在底层实现上大不相同,这些不同点主要针对写操作。
举个例子,对集合元素的添加,普通集合的add()方法,会将当前集合的长度+1,再将值存入集合下标为size的位置。而CopyOnWriteArrayList集合的add()方法,会复制当前集合的数组元素,创建一个新数组,将原数组的值复制到新数组中,再把值添加到新数组中,最后修改集合对象的引用为新数组,当然这个过程是在线程安全的前提下进行的。
源码阅读
1.构造方法
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}//初始化数组值为全0的数组
2.get()方法
private E get(Object[] a, int index) { //重载方法,获取指定数组中指定下标的元素
return (E) a[index];
}//获取指定数组指定位置的元素
//返回get()的重载方法,参数为当前数组对象和下标
public E get(int index) {
return get(getArray(), index);
}
3.set方法
final void setArray(Object[] a) {//为数组对象设置新的引用
array = a;
}
/*set的重载方法
*设置下标为index的位置元素为element
* 修改成功时,返回被修改元素的值
*/
public E set(int index, E element) {
final ReentrantLock lock = this.lock;//获取当前对象的锁,说明该方法线程安全
lock.lock();//上锁
try {
Object[] elements = getArray();//调用getArray()方法,获取当前数组
E oldValue = get(elements, index);//将原index位置的元素保存至变量oldValue
if (oldValue != element) { //如果element与原元素值不等,做修改操作
int len = elements.length;//获取当前数组的长度
Object[] newElements = Arrays.copyOf(elements, len);//将数组复制一份为newElements
newElements[index] = element;//将新数组指定下标的元素修改为指定值element
setArray(newElements);//调用setArray()方法,将当前数组对象指向newElements
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);//调用setArray()方法,指向当前对象
}
return oldValue; //返回被修改的元素
} finally {
lock.unlock();//释放锁
}
}
4.add()方法 (包含重载)
//添加一个元素到当前数组
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,并将长度++
newElements[len] = e;//将数组最后一个元素设置为参数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;//定义新的数组
//10 3=7
int numMoved = len - index;//获取需要移动的元素数量
if (numMoved == 0)//如果该值为0,说明是在该数组的最后添加一个值
newElements = Arrays.copyOf(elements, len + 1);//将原数组复制到新数组,新数组长度为原数组长度+1
else {//在数组中添加值
newElements = new Object[len + 1];//创建新数组,数组长度为原数组长度+1
/*System.arraycopy方法的功能及参数的含义
* 功能:将一个数组中的数据复制到另一个数组中
* 参数1:原数组
* 参数2:从原数组的什么位置开始复制
* 参数3:新数组
* 参数4:从新数组的什么位置开始粘贴
* 参数5:复制多少个数据
* 以117行代码为例:将elements数组从下标0开始,复制index个元素 粘贴到newElements数组从0开始的位置
* */
System.arraycopy(elements, 0, newElements, 0, index);
//将elements数组从下标index开始,复制numMoved(该值是数组中需要移动的元素个数)个元素 从newElements数组目标插入位置下一个位置开始粘贴
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;//将新数组index下标的值设置为element(传入的参数)
setArray(newElements);//调用方法设置当前数组对象引用为新数组
} finally {
lock.unlock();//释放锁
}
}
5.remove(int index)方法
//删除指定下标处元素
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; 获取要移动的元素个数
* 对于删除操作来说,是将被删除元素后的所有元素都向前移动,因此需要移动的元素个数是 数组长度-被删除元素在数组中的位置
* */
int numMoved = len - index - 1;
if (numMoved == 0)//如果移动个数为0.说明删除的是数组中的最后一个元素
/*对于147行代码分为2步
* 第一步:将数组中除最后一个元素的所有值复制,该方法返回一个数组
* 第二步,调用setArray方法,将当前数组的引用指向返回的数组
* */
setArray(Arrays.copyOf(elements, len - 1));
else {//如果删除的是数组中间的元素
Object[] newElements = new Object[len - 1];//创建一个新的数组,长度为原数组长度-1
//从原数组下标为0的位置开始复制,复制到要删除元素的前一个元素,粘贴到新数组从0开始的位置
System.arraycopy(elements, 0, newElements, 0, index);
//从原数组要删除元素的后一个元素开始复制,复制numMoved(要移动个元素个数)个,新数组从被删除元素的下标位置开始粘贴
System.arraycopy(elements, index + 1, newElements, index,
numMoved);//index=2,
setArray(newElements);//调用方法,将当前数组的引用指向新数组
}
return oldValue;//返回被删除的元素
} finally {
lock.unlock();//释放锁
}
}
6.removeRange(int fromIndex, int toIndex)方法
//删除指定范围内的元素值
void removeRange(int fromIndex, int toIndex) {//参数:删除的起始位置,结束位置(包前不包后)
final ReentrantLock lock = this.lock;//获取锁
lock.lock();//上锁
try {
Object[] elements = getArray();//获取当前数组
int len = elements.length;//获取当前数组的长度
if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)//判断删除范围的合法性
throw new IndexOutOfBoundsException();
int newlen = len - (toIndex - fromIndex);//计算删除后的数组长度
int numMoved = len - toIndex;//计算从删除范围的终止位置到数组最后还剩多少个元素
if (numMoved == 0)//如果删除元素个数为0 newlen为原数组长度
setArray(Arrays.copyOf(elements, newlen));//改变当前数组引用为当前数组复制后的新数组
else {//删除个数不为0
Object[] newElements = new Object[newlen];//创建一个长度为剩余元素个数的新数组
//将当前数组下标为0的元素到要删除范围的起始位置前一个的元素复制,粘贴到新数组中从0开始的位置
System.arraycopy(elements, 0, newElements, 0, fromIndex);//[2,5] 10-5+2=7 10-5=5;
//将原数组中下标为要删除范围的最后一个元素的后一个元素开始复制,复制numMoved个,粘贴到新数组被删除范围的起始位置
System.arraycopy(elements, toIndex, newElements,
fromIndex, numMoved);
setArray(newElements);//调用方法,将当前数组的引用修改为新数组
}
} finally {
lock.unlock();//释放锁
}
}
7.removeAll(Collection<?> c)方法
//删除参数集合与当前集合的交集
public boolean removeAll(Collection<?> c) {
if (c == null) throw new NullPointerException(); //如果集合为空,抛出异常
final ReentrantLock lock = this.lock;//获取锁
lock.lock();//上锁
try {
Object[] elements = getArray();//获取当前数组
int len = elements.length;//获取数组长度
if (len != 0) {
// temp array holds those elements we know we want to keep
int newlen = 0;//设置新数组长度为0
Object[] temp = new Object[len];//创建一个新数组
for (int i = 0; i < len; ++i) {
Object element = elements[i]; //获取原数组中的每个元素
if (!c.contains(element))//判断该元素是否在集合c中存在,如果不存在保存到新数组中
temp[newlen++] = element;//保存到新数组,下标++
}
if (newlen != len) {//若两个数组中存在相同的元素
setArray(Arrays.copyOf(temp, newlen));//调用方法,将当前数组引用指向复制新数组后返回的数组
return true;//返回true
}
}
return false;//原数组长度为0,直接返回
} finally {
lock.unlock();//释放锁
}
}
8.clear()方法
//清空数组
public void clear() {
final ReentrantLock lock = this.lock;//保证线程安全,获取锁
lock.lock();//上锁
try {
setArray(new Object[0]);//设置当前数组对象的引用为一个空数组,以达到清空原数组的目的
} finally {
lock.unlock();//释放锁
}
}
9.equals(Object o)方法
/*判断两个对象是否相等
* 对于该方法使用的三目运算符
* */
private static boolean eq(Object o1, Object o2) {
return (o1 == null) ? o2 == null : o1.equals(o2);
}
//重写equals()方法,判断两对象是否相等
public boolean equals(Object o) {
if (o == this) //先使用==判断传入的对象与当前对象的内存地址
return true;//如果内存地址,相同,则二者为同一个对象,返回
if (!(o instanceof List))//判断当前对象是否是集合类型的对象,如果不是,返回
return false;
List<?> list = (List<?>)(o);//向下转型
Iterator<?> it = list.iterator();//获取迭代器,遍历参数集合时使用
Object[] elements = getArray();//将当前集合对象转换为数组
int len = elements.length;//获取数组的长度
for (int i = 0; i < len; ++i)//遍历数组
//如果遍历到参数集合的最后一个元素,或者当前数组元素与集合的元素不相等,返回falsee
if (!it.hasNext() || !eq(elements[i], it.next()))
return false;
if (it.hasNext())//如果当前数组全部遍历结束,但参数集合还未遍历完,说明二者不相等,返回false
return false;
return true;//除去一切不可能,剩下的就算再离谱,也是真相,返回true!(突然中二,应该没人发现)
}
10.sort(Comparator<? super E> c)方法
//将数组内的元素进行排序
public void sort(Comparator<? super E> c) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();//获取当前数组
Object[] newElements = Arrays.copyOf(elements, elements.length);//创建一个新数组,并将原数组中的元素复制过去
@SuppressWarnings("unchecked") E[] es = (E[])newElements;//没啥实际用处
Arrays.sort(es, c);//调用集合工具类的排序方法,该方法会返回一个排序好的数组
setArray(newElements);//调用方法,将当前集合对象的引用指向新数组
} finally {
lock.unlock();//释放锁
}
}
11. addAll(Collection<? extends E> c)方法
//合并数组
public boolean addAll(Collection<? extends E> c) {
/*
* 创建一个数组用来保存参数集合中的元素
* 对于该数组cs,如果参数数组的类型是一个CopyOnWriteArrayList类型,则使用getArray()方法获取其数组对象
* 如果该参数数组的类型是一个普通的集合类型,则使用toArray()方法获取其数组对象
* */
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
if (cs.length == 0)//若该数组长度为0,返回false
return false;
final ReentrantLock lock = this.lock;//获取锁对象
lock.lock();//上锁
try {
Object[] elements = getArray();//获取当前集合对象的数组
int len = elements.length;//获取长度
if (len == 0 && cs.getClass() == Object[].class)
setArray(cs);
else {
//创建一个新数组,数组长度为原数组长度和要合并的数组长度,将原数组内容复制进去
Object[] newElements = Arrays.copyOf(elements, len + cs.length);
//将要合并的数组中的数据全部复制到新数组中下标从len开始的位置上
System.arraycopy(cs, 0, newElements, len, cs.length);
setArray(newElements);//调用方法,将当前数组引用指向复制新数组后返回的数组
}
return true;
} finally {
lock.unlock();//释放锁
}
}
结语
对于CopyOnWriteArrayList,该集合的特点十分明确,它能够保证线程在高并发读取下的写安全,且不需要对数组进行扩容,这是因为其对数组的操作基于复制数组。但这样的复制机制也使得其对数组的操作变得繁琐。其次由于集合不对读操作上锁,故不能保证读取操作的实时一致性。