文章目录

  • 前言
  • 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);
}



总结一下整个流程:

  1. 首先查看容量够不够
  1. 如果是空容量的数组,那么第一次扩容之后容量就是10
  2. 不是空容量的数组,也就是自己指定容量了,就按size+1来
  3. 每次扩容的倍数是原来的1.5倍,要判断这个容量有没有超过MAX_ARRAY_SIZE,超过了就用Integer.MAX_VALUE。如果没有超过MAX_ARRAY_SIZE,还要和预计的容量进行比较,也就是minCapacity,这里的minCapacity = size+1,取最大的那一个
  4. 如果本来的容量超过了最大值,那么抛出异常OutOfMemoryError
  1. 扩容完成之后就可以添加到尾部了
  2. 返回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));
}

总结流程:

  1. 检查下标越界
  2. 看看需不需要扩容
  3. 从index位置开始把之后的数据挪到index+1上
  4. 在index位置插入数据
  5. 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;
}

总结流程:

  1. 判断有没有越界
  2. 修改次数+——
  3. 找到要删除的元素,以便返回
  4. 把index之后的元素都往前移动一个
  5. 将最后一个元素置为null
  6. 返回旧的元素,删除成功


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

总结流程:

  1. 首先遍历所有的元素找到下标
  2. 修改次数++
  3. 找出要移动的元素
  4. 把elementData中index+1后到结尾的元素都移动到前面一格
  5. 最后一个元素设置为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;
}

总结流程:

  1. 修改次数++
  2. 求出要移动的元素
  3. 将toIndex到size之间的元素移动到fromIndex之后
  4. 把空出来的最后numMoved个元素设置为null,等待垃圾回收
  5. 更新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);
    }
}

java arrarylist源码 arraylist remove源码_list

所以我们知道了这个方法的作用就是在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循环

java arrarylist源码 arraylist remove源码_开发语言_02


1、C中包含1,c.contains(elementData[r]) = true, w不变,r++

java arrarylist源码 arraylist remove源码_list_03

java arrarylist源码 arraylist remove源码_开发语言_04


2、C中不包含2,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++

java arrarylist源码 arraylist remove源码_java arrarylist源码_05


3、C中不包含3,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++

java arrarylist源码 arraylist remove源码_ci_06



4、C中不包含4,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++

java arrarylist源码 arraylist remove源码_java arrarylist源码_07



5、C中不包含5,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++

java arrarylist源码 arraylist remove源码_java arrarylist源码_08



6、C中包含6,c.contains(elementData[r]) = true, w不变,r++

java arrarylist源码 arraylist remove源码_开发语言_09



7、C中包含7,c.contains(elementData[r]) = true, w不变,r++

java arrarylist源码 arraylist remove源码_ci_10



8、C中包含8,c.contains(elementData[r]) = true, w不变,r++

java arrarylist源码 arraylist remove源码_java arrarylist源码_11


9、C中不包含9,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++

java arrarylist源码 arraylist remove源码_java_12



10、C中不包含10,c.contains(elementData[r]) = false, elementData[w++] = elementData[r],r++, w++

java arrarylist源码 arraylist remove源码_java_13



11、C中包含1,c.contains(elementData[r]) = true, w不变,r++

java arrarylist源码 arraylist remove源码_开发语言_14


可以看到遍历完成之后从 w 到 r 的这部分都是需要设置为null的,也就是需要删除的。