Java之ArrayList源码分析(第三篇:删除元素)

您所在的位置:网站首页 arraylist删除元素的方法 Java之ArrayList源码分析(第三篇:删除元素)

Java之ArrayList源码分析(第三篇:删除元素)

2023-09-22 14:05| 来源: 网络整理| 查看: 265

(注意:本文基于JDK1.8)

删除元素的4个方法

清空元素的clear()方法

remove(int)方法分析 public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - 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; }

删除指定位置index(下标)的元素并返回旧的元素,传入参数index表示下标值

1、检查下标值是否符合要求

通过rangeCheck()方法完成下标值index的检查工作,检查下标值是否满足范围要求,如果传入的下标值index超过或者等于元素总数size,将抛出一个IndexOutOfBoundsException异常对象,具体的检查过程,下文会提到rangeCheck()方法的源码分析

2、fail-fast机制,防止多线程下使用ArrayList的办法

将实例变量modCount加1,表示已经产生了修改元素的行为,modCount并不在ArrayList中定义,我向上查找,发现它在基类AbstractList中定义

3、获取当前下标index中的已经保存的元素

从ArrayList对象持有的底层数组对象elementData中的下标index处取出元素对象,并赋值给局部变量oldValue持有

4、计算需要挪动的元素数量

现在需要删除一个元素,空余的位置,需要后面的元素向前移动占满,先计算下需要挪动几个元素,使用公式size - index - 1即可

举个例子:假设目前ArrayList一共保存了5个元素,此时size==5,你需要删除下标0的元素,我们用公式计算一下需要挪动几个元素(size - index - 1)。5 - 0 - 1 = 4,共计4个元素要向前“挪动”,其实是下标0之后的4个元素,依次向前复制一位,比如开始的数组是【"8","9","3","5","6"】,现在删除下标0的元素,然后下标0后面的4个元素会向前复制一位,则数组变为【"9","3","5","6","6"】,看到了吧?数组的最后一位此时还是存在的,只是第一位的元素被覆盖了……

5、向前挪动所有元素

使用System的arraycopy()方法完成“挪动元素”

第一个参数elementData表示需要挪动的源数组对象

第二个参数index+1表示从源数组对象的哪个下标处开始

第三个参数表示要拷贝到的目标数组对象

第四个参数表示从目标数组对象的哪个下标处开始拷贝

第五个参数表示复制的元素总数,挪动数量numMoved即表示需要复制的元素总数

6、将元素总数size减1

7、将无用的元素赋值为null,便于GC回收

因为所有index后面的元素都向前挪动了一位(向前复制),此时size处下标仍然保存的是未挪动前的元素对象,只有直接赋值为null,日后此对象才能被GC回收掉(不然就会造成数组多个下标指向同一个对象,造成对象无法被回收)

8、向调用者返回结果

返回旧的元素对象

rangeCheck(int)方法分析 private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }

用于检查下标值index是否符合范围的方法

1、判断传入的下标值index大于或者等于元素总数size

如果不符合要求,将执行下面的步骤2和步骤3

2、生成异常信息字符串

先是调用了一个outOfBoundsMsg()方法,outOfBoundsMsg()方法接受了下标值index,该方法返回的字符串信息会在控制台展示,在第二篇文章中已经分析了outOfBoundsMsg()方法的实现,这里不再解释

3、抛出一个IndexOutOfBoundsException异常对象

remove(Object)方法分析 public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }

用于将相等的元素对象删除的方法,传入的参数表示元素对象

因为ArrayList支持添加null作为元素,所以作者将null元素与非null元素的删除逻辑分开处理,我们分别学习一下

一、传入的元素为null

当传入的元素为null时,则会从数组对象elementData的第1个元素开始遍历,一直遍历到元素总数size-1的下标处,每1轮的迭代会判断elementData对应下标的元素值是否为null,此处使用==作为元素相等的判断依据,因为null无法使用equals()方法比较对象是否相等

1、当从ArrayList对象持有的elementData找到一个元素为null时,调用fastRemove()方法进行删除元素,并结束遍历,整个remove(Object)方法会返回true,表示找到相等的元素,并删除元素成功

2、如果遍历结束,没有在elementData中找到任何的null元素,remote(Object)方法会返回false,表示删除元素失败

二、传入的元素为非null时

传入的元素为非null,同样从底层数组对象elementData的第一个元素开始遍历,遍历过程中每一轮都会调用传入的元素Object对象的equals()方法与elementData对应下标中的元素对象进行比较,此处使用Object的equals()方法比较元素对象的相等性,equals()方法的返回结果表示相等性,true表示两个对象相等、false表示两个对象不相等

1、当在elementData中找到一个匹配的元素(此时equals()方法返回true),执行fastRemove(int)方法进行删除元素,之后遍历结束,整个remove()方法返回true,表示删除元素成功

2、当遍历结束,没有找到匹配相等的元素,整个remote(Object)方法返回false,表示删除元素失败

fastRemove(int)方法分析 private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }

删除ArrayList对象持有的elementData数组对象中元素的方法,学习一下它的源码

说明:无论删除null元素、还是删除Object元素,都会调用该方法进行删除

1、fail-fast机制,防止多线程下使用ArrayList

将表示修改次数的实例变量modCount加1

2、计算需要向前挪动的元素数量

计算公式为size - index - 1,结果保存在局部变量numMoved中

3、当需要挪动的元素大于0,执行数组元素复制,即数组元素挪动

同样使用System的静态方法arraycopy()完成元素的复制(挪动),删除操作会执行向前复制元素

4、元素总数size减去1

5、将不再使用的下标赋值为null,便于GC未来回收指向的对象

size值作为下标,将其赋值为null

removeAll(Collection)方法分析 public boolean removeAll(Collection c) { Objects.requireNonNull(c); return batchRemove(c, false); }

用于删除指定集合中的多个元素,传入一个Collection对象,表示待删除的元素集合

1、检查传入的Collection必须不能为null

调用了Objects下的方法来判断是否为null

2、调用一个batchRemove()方法完成批量删除工作

调用batchRemove()方法,同时传入一个Collection对象与一个false值

3、返回batchRemove()方法的执行结果给调用方

batchRemove()方法分析 private boolean batchRemove(Collection c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }

用于删除集合中的所有元素

1、获取ArrayList对象持有的底层数组对象elementData

首先获取当前ArrayList对象持有的底层数组对象elementData,并赋值给一个同名的局部变量elementData持有

2、定义两个局部变量

一个局部变量r表示读取下标(遍历时elementData数组时的,读取下标)

一个局部变量w表示写入下标

3、定义一个标志位

局部变量modified,初始值为false,表示什么呢?后面看看

4、检查现有ArrayList对象持有的每一个元素是否与传入的Collection对象的任意一个元素匹配,如果不匹配,说明是要保留的元素对象,此时将要保留的元素会继续存放在底层数组对象elementData中下标w处,并且下标w之后会加1,最终w坐标及之前的所有元素均为需要存入的元素,这里需要画一张图

5、针对c.ontains()方法抛出异常的情况做处理

finally代码块对c.contains可能抛出异常的情况也做了处理,如果c.contains抛出了异常,则r一定不等于size,此时将r下标后面的元素一个一个的复制到以w下标开始的数组中

6、清除无用元素占用的数组位置

将保留所有元素的后面位置,全部赋值为null,是从局部变量w开始的下标算起,size减去局部变量w的值就是要被修改的数量,此时也会对modCount做了更新,后面又更新了最新的元素总数,w的值目前就是新的元素总数,所以最后又赋值给size

7、将标志位更为true

标记到modified变量中,表示删除结果

8、返回删除结果

将modified的boolean值返回给调用方

clear()方法分析 public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }

用于清空ArrayList所有元素的方法

1、fail-fast机制,防止多线程下使用ArrayList的办法

首先将modCount增加1个,其它位置对modCount会做判断

2、将elementData数组对象中的所有元素赋值为null

将ArrayList对象持有的底层数组elementData对象中的持有的所有对象,全部赋值为null,剩下的工作就交给GC了

3、更新元素总数

元素总数size赋值为0

总结

1、modCount值用于fail-fast机制(防止多线程下使用ArrayList),添加单个元素或多个元素、删除单个元素或多个元素、清空所有元素时,均对modCount值做了+1,表示修改次数加1

2、中间删除元素,也是O(n)的时间复杂度,因为删除元素后,需要将后面的元素,一个一个的向前复制元素(表示挪动元素)

3、感叹作者想的好周全,removeIf()方法将单独分析……

4、System的静态方法arraycopy()继续作为数组元素向前"挪动"的功能,这个native方法值得学习



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3