ArrayList集合源码解读(一)

ArrayList集合源码解读(一)

前言

笔者在阅读网上众多的ArrayList源码解读时发现他们都是以1.8版本的来进行讲解,并且很多都是囫囵吞枣,看的人一脸懵逼。

其实现在的很多公司都换成了17版本的jdk。笔者决定自己写一个ArrayList集合源码的解读,笔者将直接通过17版本的jdk深入底层的带大家学习ArrayList。17版本的jdk大部分代码和1.8是一样的,只要极少部分存在漏洞的代码进行了优化。所以大家学完17版本的后,1.8版本的肯定都可以看懂,并且各位还能发现1.8的一些实现漏洞。


ArrayList 底层是基于 Object[] 数组实现的。与 Java 中的静态数组相比,它的容量能动态增长。可以理解为 ArrayList 是一种动态数组。那么今天我们就来阅读下源码,看看 Java 是如何实现这个动态数组的集合。

注意:ArrayList 适用于频繁的查找工作,是一个线程不同步的集合,这点他与 LinkedList 相同。


首先,我们照例看一下 ArrayList 的类定义

public class ArrayList<E> 
        extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}   

一. 继承和实现关系

ArrayList 类继承了 AbstractList 抽象类。

  • 我们可以点进源码看一下作者对 AbstractList 抽象类的描述:此类提供了List接口的骨架实现,以最大限度地减少实现此接口所需的工作量。

ArrayList 实现了 List , RandomAccess , Cloneable , Serializable 这些接口。

  • List : **表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。 **

  • RandomAccess这是一个标志接口,表明实现这个接口的 List 集合支持 快速随机访问

    • 什么是快速随机访问? -> 可以通过索引下标快速访问元素就叫做快速随机访问
  • CloneableCloneable 是一个标记接口,我们点进去会发现它并没有任何方法。此接口表明ArrayList具有拷贝能力,可以进行深拷贝或浅拷贝操作 。

    package java.lang;
    
    public interface Cloneable {
    }
    
  • Serializable : 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便。
    在这里插入图片描述


二. 核心源码解读

笔者以 JDK17 为例,分析一下 ArrayList 的底层源码。

2.1. 属性及构造函数解读

ArrayList 提供了三个构造方法帮助我们创建对象。我们来详细分析下这三个构造方法实现的底层逻辑。

  • ArrayList(int initialCapacity)通过给定的 initialCapacity 创建集合对象。
  • ArrayList()无参构造方法,创建一个初始值为空数组的集合对象。
    • 注意: 以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10
  • ArrayList(Collection<? extends E> c)传入一个集合,构造一个包含此集合中所有元素的集合对象。

源码:

	// 默认初始容量大小
    private static final int DEFAULT_CAPACITY = 10;

    // 空数组(用于空实例)
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //用于默认大小空实例的共享空数组实例。
    //我们把它从 EMPTY_ELEMENTDATA 数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 保存ArrayList数据的数组
    transient Object[] elementData; // non-private to simplify nested class access

    // 元素个数
    private int size;

    /**
     * 带初始容量参数的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小)
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //如果传入的参数大于0,创建initialCapacity大小的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            // 传入的值为 0,则创建一个空数组
            this.elementData = EMPTY_ELEMENTDATA;  
        } else {  // 不合规,报错
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     *  默认无参构造函数
     */
    public ArrayList() {
        // 初始化为一个空数组
        // 在添加第一个元素时,会将容量扩容为 10  (后面笔者会专门讲解扩容源码,各位有个印象即可)
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 传入一个集合,构造一个包含此集合中所有元素的列表,按照它们由集合的迭代器返回的顺序。
     */
    public ArrayList(Collection<? extends E> c) {
        // 将指定集合转换成数组
        Object[] a = c.toArray();
        // (size = a.length) != 0 做了两个操作
        // 1.将 a.length 的值赋值给 size
        // 2.判断 size 是否不等于 0
        if ((size = a.length) != 0) {
            // 如果 elementData 是 ArrayList 类型数据,就直接赋值
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                // 如果不是则转换成 Object[] 数组再赋值(因为 ArrayList 底层是 Object[] 数组)
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 如果给定集合 c 的长度为 0,则替换成空数组
            elementData = EMPTY_ELEMENTDATA;
        }
    }

2.2 核心扩容方法解读

ArrayList 的扩容机制提高了性能,如果每次只扩充一个,那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList 的扩容机制避免了这种情况。

源码:

 /**
  * 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量
  *
  * @param minCapacity 所需的最小容量
  */
public void ensureCapacity(int minCapacity) {
    if (minCapacity > elementData.length // 要保证传入的 minCapacity 大于当前集合中数据长度
        && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA  // elementData不能为空
             && minCapacity <= DEFAULT_CAPACITY)) {  // 传入的 minCapacity 要大于默认长度10
        modCount++;  // 版本号控制
        // 扩容底层实现方法,调用此方法代表已经开始扩容了
        grow(minCapacity);
    }
}


/**
 * 增加容量,以确保它至少可以容纳最小容量参数指定的元素数量
 *
 * @param minCapacity 所需的最小容量
 * @throws OutOfMemoryError 如果 minCapacity 小于 0,抛出异常
 */
private Object[] grow(int minCapacity) {
    // 获取当前集合中已有的元素个数
    // oldCapacity为旧容量,newCapacity 为新容量
    int oldCapacity = elementData.length;
    // 如果 oldCapacity > 0 或者 集合中的元素不为空数组,则进行扩容
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 扩容的主体代码,其实简单来说就是将新数组扩容成老数组的 1.5 倍
        // 大家要知道位运算的速度远远快于整除运算,所以看似炫技的写法 oldCapacity >> 1 其实是为了性能考虑
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                          minCapacity - oldCapacity,  // 判断要增加几个元素位置
                          oldCapacity >> 1);  // >> 1 等价于 / 2  eg: 5 >> 1 = 5 / 2 = 3
        // 将原数据拷贝到新数组长度的数组中
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        // 进入 else 则证明我们的 oldCapacity <= 0 或者 elementData 为u空数组,则进行下方的操作
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}


/**
* 要分配的最大数组大小
*/
 public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;


/**
* 根据给定的参数返回一个新长度
*
* @param oldLength   数组的当前长度(必须为非负)
* @param minGrowth   所需的最小增长量(必须为正)
* @param prefGrowth  首选增长量
* @return 新数组长度
* @throws OutOfMemoryError 如果新长度超过 Integer.MAX,报错
*/
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
    // 大家看上方的官方解释可能很懵逼。什么是所需的最小增长量?什么是首选增长量?
    // 其实通俗的理解,就是比较 当前数组的长度的一半 (oldLength >> 1) 和 我们期望增加的长度 (minCapacity - oldCapacity) 谁更大,就用谁
    int prefLength = oldLength + Math.max(minGrowth, prefGrowth); 
    // 合法性检测   SOFT_MAX_ARRAY_LENGTH:要分配的最大数组大小
    if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
        return prefLength;
    } else {
        // else 中的逻辑,此处涉及不到,感兴趣的同学可以自己去研究下
        return hugeLength(oldLength, minGrowth);
    }
}

2.3 常用方法解读

未完待续~

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值