文章目录
- 前言
- 1. add
- 1. public boolean add(E e)
- 2. public void add(int index, E element)
- 3. public boolean addAll(Collection<? extends E> c)
- 4. public boolean addAll(int index, Collection<? extends E> c)
- 2. remove
- 1. public E remove(int index)
- 2. public boolean remove(Object o)
- 3. protected void removeRange(int fromIndex, int toIndex)
- 4. public boolean removeAll(Collection<?> c)
前言
这一部分将会介绍源码中的add方法和remove方法
1. add
1. public boolean add(E e)
将指定的元素追加到此列表的末尾
//参数e
public boolean add(E e) {
//调用方法对内部进行校验,看需不需要扩容,传入的参数是size+1,也就是添加元素后的大小
ensureCapacityInternal(size + 1);
//校验成功或者扩容成功之后,就可以追加到尾部了
elementData[size++] = e;
return true;
}
//minCapacity = size + 1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//计算要判断的容量,得到一个准确的容量大小
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断集合存数据的数组是否等于空容量的数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//通过最小容量和默认容量 求出较大值 (用于第一次扩容)
//DEFAULT_CAPACITY = 10,也就是说第一次扩容的长度是10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果不是空的数组,那么长度就是本身,用于自定义长度的集合
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
//实际修改集合次数++ (在扩容的过程中没用,主要是用于迭代器中)
modCount++;
//判断如果后来的容量比数组长度要大,证明这时候就要扩容了
if (minCapacity - elementData.length > 0)
//进行扩容,扩容的方法在上一节有
grow(minCapacity);
}
总结一下整个流程:
- 首先查看容量够不够
- 如果是空容量的数组,那么第一次扩容之后容量就是10
- 不是空容量的数组,也就是自己指定容量了,就按size+1来
- 每次扩容的倍数是原来的1.5倍,要判断这个容量有没有超过MAX_ARRAY_SIZE,超过了就用Integer.MAX_VALUE。如果没有超过MAX_ARRAY_SIZE,还要和预计的容量进行比较,也就是minCapacity,这里的minCapacity = size+1,取最大的那一个
- 如果本来的容量超过了最大值,那么抛出异常OutOfMemoryError
- 扩容完成之后就可以添加到尾部了
- 返回true
2. public void add(int index, E element)
在指定位置添加元素
public void add(int index, E element) {
//检查下标越界的问题
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); //检查容量,顺便修改次数++
//调用arraycopy方法,把原来数组从index开始,复制到新数组的index+1的位置
//由于上面一步已经扩容了,所以下面不用担心越界的问题
//复制的长度是size-index。意思就是假设需要从index插入,就要将size-index之后的数据从index复制到index+1的1位置
//然后再进行插入
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//在index位置插入即可
elementData[index] = element;
//size++
size++;
}
private void rangeCheckForAdd(int index) {
//如果下标大于size或者下标小于0,就是越界处理
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
总结流程:
- 检查下标越界
- 看看需不需要扩容
- 从index位置开始把之后的数据挪到index+1上
- 在index位置插入数据
- size++
3. public boolean addAll(Collection<? extends E> c)
在list中添加一个集合
public boolean addAll(Collection<? extends E> c) {
//首先把集合转化为数组
Object[] a = c.toArray();
//求出数组长度
int numNew = a.length;
//和上面一样,判断长度,这次是size+numNew,有需要扩容就进行扩容
ensureCapacityInternal(size + numNew);
//然后通过arraycopy方法把a数组中的元素复制到list集合中的size位置,之后就完成添加了
System.arraycopy(a, 0, elementData, size, numNew);
//size + numNew
size += numNew;
//如果是0,也就是空集合,就返回false
return numNew != 0;
}
总结流程:
- 首先将集合转化为数组
- 求出数组长度
- 然后判断size + numNew是否超出容量了。如果超过了就进行扩容
- 通过arraycopy方法把数组中的数据复制到集合中去
- 最后更新size
4. public boolean addAll(int index, Collection<? extends E> c)
在指定的位置插入一个集合
public boolean addAll(int index, Collection<? extends E> c) {
//校验索引
rangeCheckForAdd(index);
//集合转化为数组
Object[] a = c.toArray();
//求长度
int numNew = a.length;
//然后判断有没有需要扩容
ensureCapacityInternal(size + numNew); // Increments modCount
//要移动的长度,size - index,表示要把index之后的那些数据都移动
int numMoved = size - index;
if (numMoved > 0)
//这里只是把index之后的数据移动到index+numNew的位置,空出中间的空缺
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//下面才是真正的复制数据,把a的数据复制到elementData的index位置,插入成功
System.arraycopy(a, 0, elementData, index, numNew);
//size更新
size += numNew;
//如果numNew = 0,是空集合就返回false
return numNew != 0;
}
2. remove
1. public E remove(int index)
根据下标移除元素
public E remove(int index) {
//判断下标有没有超过界限
rangeCheck(index);
//修改次数++
modCount++;
//找到要删除的元素
E oldValue = elementData(index);
//然后这是需要移动的元素,比如一个size = 4的list,要移除下标为1的位置,那么应该让 4-1-1 = 2,也就是
//下标1后面的2和3移动到前面来
int numMoved = size - index - 1;
//如果>0, 表示有数据可以移动,如果没有,那么表示删除的就是最后一个
if (numMoved > 0)
//把index+1直到最后的数据都移动到前面来
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//然后size--,并且把原来list的最后一个复制为null,完成移除
elementData[--size] = null;
//返回旧的元素
return oldValue;
}
总结流程:
- 判断有没有越界
- 修改次数+——
- 找到要删除的元素,以便返回
- 把index之后的元素都往前移动一个
- 将最后一个元素置为null
- 返回旧的元素,删除成功
2. public boolean remove(Object o)
根据元素删除
public boolean remove(Object o) {
//如果o是null
if (o == null) {
//从头开始遍历
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//找到所有null的元素,删除掉
fastRemove(index);
return true;
}
} else {
//不是null
for (int index = 0; index < size; index++)
//就使用equals判断
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//删除元素
private void fastRemove(int index) {
//修改次数++
modCount++;
//要移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//把elementData中index+1后到结尾的元素都移动到前面一格
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//最后一个元素设置为null
elementData[--size] = null;
}
总结流程:
- 首先遍历所有的元素找到下标
- 修改次数++
- 找出要移动的元素
- 把elementData中index+1后到结尾的元素都移动到前面一格
- 最后一个元素设置为null
3. protected void removeRange(int fromIndex, int toIndex)
这是ArrayList内部的一个方法,从fromIndex到toIndex的元素都删除掉,应该是不包括toIndex的,[fromIndex, toIndex)
protected void removeRange(int fromIndex, int toIndex) {
//修改次数++
modCount++;
//要移动的元素个数
//比如list有10个,要从下标5删除到下标8,那么
int numMoved = size - toIndex;
//将toIndex之后的元素移动到fromIndex这边来
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
//新的大小,上面的例子中,新的大小就是 10-(8-5)=7, 也就是删除了5、6、7
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
//设置为null,等待GC回收
elementData[i] = null;
}
//设置新的大小
size = newSize;
}
总结流程:
- 修改次数++
- 求出要移动的元素
- 将toIndex到size之间的元素移动到fromIndex之后
- 把空出来的最后numMoved个元素设置为null,等待垃圾回收
- 更新size大小
4. public boolean removeAll(Collection<?> c)
首先来看一下效果:
public class sourceTest {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
arrayList.add(4);
arrayList.add(5);
arrayList.add(6);
arrayList.add(7);
arrayList.add(1);
ArrayList<Integer> arrayList1 = new ArrayList<>();
arrayList1.add(1);
arrayList1.add(2);
arrayList.removeAll(arrayList1);
System.out.println(arrayList);
}
}
所以我们知道了这个方法的作用就是在list中去除另一个集合c中包含的元素。
public boolean removeAll(Collection<?> c) {
//判断c是不是null,如果是就抛出异常
Objects.requireNonNull(c);
//批量删除
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
//获取list中的数组
final Object[] elementData = this.elementData;
//读指针r,写指针w
int r = 0, w = 0;
//初始化为false
boolean modified = false;
try {
//首先遍历整个集合
for (; r < size; r++)
//上面传过来的参数complement就是false
//意思就是如果c不包含list中的这个元素
if (c.contains(elementData[r]) == complement)
//不包含证明不需要移除,那么w可以向右移动
elementData[w++] = elementData[r];
} finally {
//如果r != size, 证明上面的遍历提前结束了,意思就是遇到了异常情况
//需要和AbstractCollection保持同步
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
//w指针要加上还没有遍历到的部分,防止for异常退出
w += size - r;
}
//如果w不等于size,证明有数据被移除了
if (w != size) {
//从w到size,把这些数据设置为null
for (int i = w; i < size; i++)
elementData[i] = null;
//修改次数更新
modCount += size - w;
//更新大小
size = w;
//设置为已修改
modified = true;
}
}
//正常情况下返回true
return modified;
}
下面通过一个图来表示整个for循环
1、C中包含1,c.contains(elementData[r]) = true, w不变,r++
2、C中不包含2,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++
3、C中不包含3,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++
4、C中不包含4,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++
5、C中不包含5,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++
6、C中包含6,c.contains(elementData[r]) = true, w不变,r++
7、C中包含7,c.contains(elementData[r]) = true, w不变,r++
8、C中包含8,c.contains(elementData[r]) = true, w不变,r++
9、C中不包含9,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++
10、C中不包含10,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++
11、C中包含1,c.contains(elementData[r]) = true, w不变,r++
可以看到遍历完成之后从 w 到 r 的这部分都是需要设置为null的,也就是需要删除的。