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

那么以上三种遍历方式有何区别呢?

  1. for循环我们很熟悉了,就是根据下标来获取元素,这个特性主要用于对数组进行遍历;
  2. 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

在这里插入图片描述

特点如下:

  1. 有序(存入和取出的顺序一致)
  2. 通过索引访问元素
  3. 允许重复, 允许多个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中实现过的方法,在这里再次被重写,我们稍后就可以看到它们的区别。

  1. 一些简单的方法:

在这里插入图片描述

  1. 增删改查方法:

数据操作最重要的就是增删改查,改查都不涉及长度的变化,而增删就涉及到动态调整大小的问题,我们先看看改和查是如何实现的:

在这里插入图片描述

  • 增和删是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
posted @   青松城  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示