点赞在看,养成习惯。
点赞收藏,人生辉煌。
点击关注【微信搜索公众号:编程背锅侠】,防止迷路。
转载请标注来源出处,谢谢合作。
前言
看源码血泪史
刚开始工作面试的时候,面试官经常问ArrayList源码相关的问题,基本上都是这部分很快结束战斗。
- 面试官:你看过ArrayList的源码吗?
- 我:你肯定会说看过呀。
- 面试官:那你来讲讲你对ArrayList源码的理解吧。
- 我:底层的数据结构是object数组;增删快、查询慢等等,没说几句就完了。
其实看了ArrayList的源码以后,你会发现能说的点还是有很多的。
比如ArrayList的构造方法的底层数组真的是构造了一个长度为10的数组吗?
Arrays.copy方法,grow扩容方法是怎么扩容的?等等都可以细说。
ArrayList的源码从工作到现在大概看了不下10遍,这其中包括看了半道放弃的。
刚开始看源码是在一些博客网站上看,看的稀里糊涂不是很明白,越看越想放弃。
后面看了一些公开课,跟着老师讲的视频看源码,看完之后感觉有点意思。但是看完之后,自己单独看还是有点吃力。
2020年4月份的时候看了一遍ArrayList源码并且每行都做了注释,整理在了有道上。
现在是七月初时隔两个月在再次看源码发现以前的笔记有部分是模糊、或者理解不正确的。
目前我发布出来的ArrayList源码是我一步一步DEBUG调试验证的源码。如果理解有问题看过之后,还请多多指教。
ArrayList系列文章
第一篇:ArrayList中的构造方法源码在面试中被问到了…抱歉没准备好!!!告辞
第二篇:面试官让我讲ArrayList中add、addAll方法的源码…我下次再来
第三篇:工作两年还没看过ArrayList中remove、removeAll、clear方法源码的都来报道吧
第四篇: 乱披风锤法锤炼ArrayList源码中的get、set、contains、isEmpty方法!!!肝起来
第五篇: 满屏飘红,操作ArrayList的Iterator方法时竟然给我报ConcurrentModificationException异常,撸ta
删除方法表格
方法名 | 描述 |
---|---|
public E remove(int index) | 根据索引删除元素 |
public boolean remove(Object o) | 根据元素删除元素 |
public void clear() | 将集合清空 |
public boolean removeAll(Collection<?> c) | 删除与给定集合中相同的元素 |
public E remove(int index) 根据索引删除元素
案例演示
@Test
public void test_remove_index(){
ArrayList<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
list.add("洛洛03");
list.forEach(System.out::println);
// 索引删除
list.remove(1);
list.forEach(System.out::println);
}
源码分析
public E remove(int index) {
// 校验这个索引是否在集合中存在
rangeCheck(index);
// 记录修改的次数
modCount++;
// 将index对应的元素赋值给 oldValue
E oldValue = elementData(index);
// 计算集合中需要移动元素个数
int numMoved = size - index - 1;
// 判断要移动的元素个数是否大于0
if (numMoved > 0)
// 能进到这里面要删除的元素肯定不在集合的最后面
// 如果需要移动元素个数大于0,就使用arrayCopy方法进行拷贝
// 注意:数据源和目标数据都是elementData
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将源集合最后一个元素置为null,尽早让垃圾回收机制对其进行回收
elementData[--size] = null; // clear to let GC do its work
// 返回被删除的元素
return oldValue;
}
elementData数组中元素的变化
-
源数组中的元素
-
System.arraycopy执行前数组中元素
- System.arraycopy执行后数组中元素-1
- System.arraycopy执行后数组中元素-2
总结
根据索引删除元素,返回被删除的元素。重点关注elementData数组中元素的变化,可以帮助理解。
public boolean remove(Object o) 根据元素删除元素
案例演示
@Test
public void test_remove_v(){
ArrayList<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
list.add("洛洛03");
list.add("洛洛04");
list.forEach(System.out::println);
// 值删除
list.remove("洛洛03");
list.forEach(System.out::println);
}
源码分析
public boolean remove(Object o) {
// 判断要删除的元素是否为null
if (o == null) {
// 被删除的元素为null,遍历这个集合
for (int index = 0; index < size; index++)
// 判断集合的元素是否为null
if (elementData[index] == null) {
// 如果相等,调用fastRemove方法快速删除
fastRemove(index);
return true;
}
} else {
// 被删除的元素不为空,遍历集合
for (int index = 0; index < size; index++)
// 用o对象的equals方法和集合每一个元素进行比较
if (o.equals(elementData[index])) {
// 如果相等,调用fastRemove方法快速删除
fastRemove(index);
return true;
}
}
// 如果集合没有o该元素,那么就会返回false
return false;
}
// 根据索引快速删除方法
private void fastRemove(int index) {
// 记录修改的次数
modCount++;
// 计算要移动元素的个数
int numMoved = size - index - 1;
// 如果需要移动的个数大于0,调用arrayCopy方法进行拷贝,判断是不是在尾部插入,大于0不是在尾部
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将集合最后一个元素置为null,尽早被释放
elementData[--size] = null; // clear to let GC do its work
}
elementData数组中元素的变化
-
源数组中的元素
-
System.arraycopy执行前数组中元素
-
System.arraycopy执行后数组中元素
总结
根据给定的元素删除集合中与之匹配的元素。返回值为是否删除成功的布尔值。
public void clear()将集合清空
案例演示
@Test
public void test_clear(){
ArrayList<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
list.forEach(System.out::println);
list.clear();
list.forEach(System.out::println);
}
源码分析
public void clear() {
// 实际修改集合次数++
modCount++;
// 遍历集合,将集合每一个索引对应位置上的元素都置为null,尽早让其释放
for (int i = 0; i < size; i++)
elementData[i] = null;
// 集合长度更改为0
size = 0;
}
elementData数组中元素的变化
-
源数组中的元素
-
清空以后的数组
总结
将集合清空。这个方法会将集合每一个索引对应位置上的元素都置为null,为的是尽早让垃圾收集器回收。
public boolean removeAll(Collection<?> c)删除与给定集合中相同的元素
案例演示
@Test
public void test_remove_all(){
ArrayList<String> list = new ArrayList<>();
list.add("洛洛01");
list.add("洛洛02");
list.forEach(System.out::println);
ArrayList<String> all = new ArrayList<>();
all.add("洛洛01");
all.add("洛洛05");
list.removeAll(all);
list.forEach(System.out::println);
}
源码分析
public boolean removeAll(Collection<?> c) {
// 校验集合是否为空,为空抛出空指针异常
Objects.requireNonNull(c);
// 批量删除
return batchRemove(c, false);
}
elementData数组中元素的变化
-
源数组中的元素
-
源数组变化以后的元素
总结
删除与给定集合中相同的元素。这个方法的主要实现是
batchRemove
方法。
private boolean batchRemove(Collection<?> c, boolean complement)批量删除方法
private boolean batchRemove(Collection<?> c, boolean complement) {
// 将原始数组的地址赋值给elementData
final Object[] elementData = this.elementData;
// r:用于遍历原始数组,原始数组中元素的索引, w:记录的是未被删除元素的个数
int r = 0, w = 0;
// modified:是否删除成功给个默认值false
boolean modified = false;
try {
// 遍历原始数组,size为原始数组的长度
for (; r < size; r++)
// complement的给定的值为false,判断指定的集合c是否不包含这个元素
if (c.contains(elementData[r]) == complement)
// 指定的集合c中不包含原始数组中的元素,将这个元素放到elementData数组中。
// 这个循环执行完毕,elementData数组中存放的就是从索引0开始存放未被删除的元素,和后面可能有要被删除的和未被删除的元素,总的长度是原始数组的size。被删除的元素会留在原位置,未被删除的元素原位置有一份,还有一份复制到前面。
elementData[w++] = elementData[r];
} finally {
// 正常情况下r == size的,这个不等于是抛出了异常
if (r != size) {
// 数组的拷贝,参看我的其他文章,有这个方法的源码详解
System.arraycopy(elementData, r,
elementData, w,
size - r);
// 计算修改的次数
w += size - r;
}
// 判断w【记录未被删除的元素的个数】是否等于元素数组的长度
if (w != size) {
// clear to let GC do its work,方便垃圾回收,将elementData数组从索引i=w开始,置空每个元素
for (int i = w; i < size; i++)
// 置空元素
elementData[i] = null;
// size - w删除元素的个数,modCount记录的是修改的次数,每删除一个元素modCount加1
modCount += size - w;
// 删除执行完以后集合的长度
size = w;
// 删除成功modified 赋值为true
modified = true;
}
}
// 返回是否删除成功
return modified;
}
创作不易, 非常欢迎大家的点赞、评论和关注(^_−)☆
你的点赞、评论以及关注是对我最大的支持和鼓励,而你的支持和鼓励
我继续创作高质量博客的动力 !!!