Java面试重点_6. ArrayList 源码分析及其扩容机制总结
文章目录
一, 单列集合(Collections)整体架构图
在 Java 中,集合大致可以分为两大体系,一个是 Collection,另一个是 Map,都位于java.util包下。
Collection :主要由 List、Set、Queue 接口组成,List 代表有序、重复的集合;其中 Set 代表无序、不可重复的集合;Java 5 又增加了 Queue 体系集合,代表队列集合。
1.集合常用的三种遍历方式
Iterator, 迭代器, 所有单例集合(Collections)的超级父类, 对各种单列集合
当我们想要遍历集合时,Java为我们提供了多种选择,通常有以下三种写法:
- 写法1: for循环
for(int i = 0, len = strings.size(); i < len; i++){
System.out.println(strings.get(i));
}
- 写法2: foreach循环(增强for循环)
for(String var : strings){
System.out.println(var);
}
- 写法3: Iterator(迭代器)
Iterator it = strings.iterator();
while(iterator.hasNext()){
System.out.println(iteraator.next());
}
那么以上三种遍历方式有何区别呢?
- for循环我们很熟悉了,就是根据下标来获取元素,这个特性主要用于对数组进行遍历;
- foreach则主要对类似链表的结构提供遍历支持, 链表没有下标, 所有使用for循环遍历会大大降低性能. Iterator实际上就是foreach.
2. Iterable 接口
- Iterator是迭代器的意思,作用是为集合类提供for-each循环的支持。
- 由于使用for循环需要通过位置获取元素,而这种获取方式仅有数组支持,
- 其他许多数据结构,比如链表,只能通过查询获取数据,这会大大的降低效率。- Iterable就可以让不同的集合类自己提供遍历的最佳方式。
迭代器和反向迭代器的具体使用: 点我
3. Collection (单例集合超级接口)
- Collection: List, Queue, Set 的超集;
- 直接继承Iterable, 所以所有的Collection接口实现类都支持for-each循环;
3.1 Collection的公用方法
3.2 AbstractCollection
在Collection中定义的许多方法,根据现有的定义以及继承的Iterable,都可以在抽象类中实现,这样可以减少实现类需要实现的方法,这个抽象类就是AbstractCollection。
3.3 List
特点如下:
- 有序(存入和取出的顺序一致)
- 通过索引访问元素
- 允许重复, 允许多个null
List集合的类继承结构图
3.3.1 List中不同于Collection的方法
- Collection主要提供一些通用的方法,而List则针对线性表的结构,提供了对位置以及子表的操作。
//在指定位置,将指定的集合插入到当前的集合中
boolean addAll(int index, Collection<? extends E> c);
//这是一个默认实现的方法,会通过Iterator的方式对每个元素进行指定的操作
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
//排序,依据指定的规则对当前集合进行排序,可以看到,排序是通过Arrays这个工具类完成的。
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
//获取指定位置的元素
E get(int index);
//修改指定位置元素的值
E set(int index, E element);
//将指定元素添加到指定的位置
void add(int index, E element);
//将指定位置的元素移除
E remove(int index);
//返回一个元素在集合中首次出现的位置
int indexOf(Object o);
//返回一个元素在集合中最后一次出现的位置
int lastIndexOf(Object o);
//ListIterator继承于Iterator,主要增加了向前遍历的功能
ListIterator<E> listIterator();
//从指定位置开始,返回一个ListIterator
ListIterator<E> listIterator(int index);
//返回一个子集合[fromIndex, toIndex),非结构性的修改返回值会反映到原表,反之亦然。
//如果原表进行了结构修改,则返回的子列表可能发生不可预料的事情
List<E> subList(int fromIndex, int toIndex);
3.4 AbstractList
3.5 ArrayList
ArrayList 是 Vector的翻版, 只是去除了线程安全;
ArrayList 是一个可以动态调整大小的List实现, 数据的取出顺序和插入顺序始终一致, 其余特性与List中定义的一致;
- 可以看到, ArrayList是Abstract的子类, 同时实现了List接口;
- 除此之外, ArrayList还实现了三个标识型接口, 这几个接口都没有任何方法, 仅作为标识表示实现类具备某项功能;
- RandomAccess表示实现类支持快速随机访问,
- Cloneable表示实现类支持克隆,具体表现为重写了clone方法,
- java.io.Serializable则表示支持序列化,如果需要对此过程自定义,可以重写writeObject与readObject方法。
1. ArrayList的构造方法和初始化
构造方法的实现:
2. ArrayList的重要方法
ArrayList已经是一个具体的实现类了,所以在List接口中定义的所有方法在此都做了实现。其中有些在AbstractList中实现过的方法,在这里再次被重写,我们稍后就可以看到它们的区别。
- 一些简单的方法:
- 增删改查方法:
数据操作最重要的就是增删改查,改查都不涉及长度的变化,而增删就涉及到动态调整大小的问题,我们先看看改和查是如何实现的:
- 增和删是ArrayList最重要的部分,这部分代码需要我们细细研究,我们看看它是如何处理我们例子中的问题的:
以上两种添加数据的方式都调用到了ensureCapacityInternal
这个方法,我们看看它是如何完成工作的:
注意: 在这之前, 先对下面这俩成员属性留个印象;
- 笔者当时读到 Math.max(DEFAULT_CAPACITY, minCapacity); 这行代码的时候有点小小的困惑,既然elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 说明当前的 ArrayList 是空的,那么直接返回DEFAULT_CAPACITY 值不就行了么,为啥还要比较呢。
- 直到后来发现了
addAll(Collection<? extends E> c)
这个方法,addAll 方法可以一次向 ArrayList 中添加多个元素, - 新增加的元素个数可能大于 DEFAULT_CAPACITY ,为了减少扩容次数,应该取 DEFAULT_CAPACITY 和 minCapacity 的最大值。
- 直到后来发现了
补充
minCapacity 等于 ArrayList 当前实际元素个数size + 新增的元素个数,minCapacity是扩容后 Object 数组的最小长度。
ensureExplicitCapacity 方法确保 ArrayList 有足够的容量存放新的元素。
A. ArrayList 扩容机制总结
- 参考文章:
- https://www.jianshu.com/p/15e8e72ad0c8
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)