ArrayList源码解读

文章目录

  • 一、故事背景
  • 二、知识点主要构成
    • 1、创建ArrayList对象-构造方法
    • 2、添加元素-add方法
      • 2.1、扩容操作
    • 3、更新元素-set方法
    • 4、删除元素-remove方法
      • 4.1、remove(int index)
      • 4.2、remove(Object o)
      • 4.3、fastRemove
    • 5、查找元素-indexOf和lastIndexOf方法
      • 5.1、indexOf
      • 5.2、lastIndexOf
  • 三、总结提升

一、故事背景

最近在学习Java中集合框架的底层源码,学习这些高人写出来的东西你才有可能成为高人,今天给大家带来的是ArrayList相关方法的源码解读;

二、知识点主要构成

1、创建ArrayList对象-构造方法

通过查看源码我们得知,ArrayList的底层是由一个数组来实现的,也就是this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;代码。在创建一个ArrayList的对象的时候同时创建一个空的数组.DEFAULTCAPACITY_EMPTY_ELEMENTDATA,可以看到DEFAULTCAPACITY_EMPTY_ELEMENTDATA在源码中的定义为一个空的数组private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};所以当我们走无参构造来创建一个ArrayList对象的时候,数组的大小默认是0

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

/**
 * Constructs an empty list with an initial capacity of ten.
 */
//创建一个空的数组。DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
//可以看到DEFAULTCAPACITY_EMPTY_ELEMENTDATA在源码中的定义为一个空的数组
//private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//所以当我们走无参构造来创建一个ArrayList对象的时候,数组的大小默认是0
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2、添加元素-add方法

添加一个元素(调用 add() 方法时)的时间复杂度最好情况为 O(1),最坏情况为 O(n)。

  • 如果在列表末尾添加元素,时间复杂度为 O(1)。
  • 如果要在列表的中间或开头插入元素,则需要将插入位置之后的元素全部向后移动一位,时间复杂度为 O(n)。
arrayList.add("a");

可以通过 add() 方法向 ArrayList 中添加一个元素。
看一下add 方法到底执行了哪些操作。首先是add方法的源码。modCount属性是用来标记当前arrayList对象被修改的次数,修改是指那些改变列表大小的修改,我们的elementData数组默认大小为0,此时当我们添加一个新的元素的时候,就改变了List的大小,所以在add方法中modCount的值会递增;同样的道理在删除一个元素的时候也会进行modCount++操作;

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
//参数 e 为要添加的元素,此时的值为“a”,size 为 ArrayList 的长度,此时为 0。
public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}
可以看到里面又调用了一个add的重载方法,接下来看一下add方法真正的执行:
/**
 * This helper method split out from add(E) to keep method
 * bytecode size under 35 (the -XX:MaxInlineSize default value),
 * which helps when add(E) is called in a C1-compiled loop.
 */
private void add(E e, Object[] elementData, int s) {
    
    //首先拿当前数组的大小和elementData的长度作比较,如果一样大说明我们的数组已经存满对象了,
    就要执行扩容操作;如果没有满则直接将该元素存入当前索引为数组大小的位置
    if (s == elementData.length)
        //如果需要增加容量,则调用 grow 方法,就是ArrayList执行扩容操作的方法
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

下面来看grow方法源码:
这里指定Arrays.copyof方法为了将当前数组复制到一个新数组中,长度为 minCapacity

2.1、扩容操作

/**
 * 扩容 ArrayList 的方法,确保能够容纳指定容量的元素
 * @param minCapacity 指定容量的最小值
 */
private int newCapacity(int minCapacity) {
    // 检查是否会导致溢出,oldCapacity 为当前数组长度
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);// 扩容至原来的1.5倍
    if (newCapacity - minCapacity <= 0) {// 如果还是小于指定容量的最小值
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // 使用 DEFAULT_CAPACITY 和指定容量的最小值中的较大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return minCapacity;
    }
	// 如果超出了数组的最大长度
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
    	//扩容至数组的最大长度
        : hugeCapacity(minCapacity);
}

以上内容就是ArrayList添加元素的全部操作;

3、更新元素-set方法

/**
 * 用指定元素替换指定位置的元素。
 * @param index   要替换的元素的索引
 * @param element 要存储在指定位置的元素
 * @return 先前在指定位置的元素
 * @throws IndexOutOfBoundsException 如果索引超出范围,则抛出此异常
 */
public E set(int index, E element) {
    // 检查索引是否越界
    Objects.checkIndex(index, size);
    // 获取原来在指定位置上的元素
    E oldValue = elementData(index);
    // 将新元素替换到指定位置上
    elementData[index] = element;
    // 返回原来在指定位置上的元素
    return oldValue;
}

该方法会先对指定的下标进行检查,看是否越界,然后替换新值并返回旧值。

4、删除元素-remove方法

remove(int index) 方法用于删除指定下标位置上的元素,remove(Object o) 方法用于删除指定值的元素。
先来看 remove(int index) 方法的源码:

4.1、remove(int index)

/**
 * 删除指定位置的元素。
 * @param index 要删除的元素的索引
 * @return 先前在指定位置的元素
 * @throws IndexOutOfBoundsException 如果索引超出范围,则抛出此异常
 */
public E remove(int index) {
	// 检查索引是否越界
    Objects.checkIndex(index, size);
    final Object[] es = elementData;
    @SuppressWarnings("unchecked") E oldValue = (E) es[index];//获取要删除的元素
	//执行删除操作;
	/**
       * 如果移除的是列表末尾的那个元素,就直接用null填补此位置。
       * 移除其他位置的元素都需要通过数组拷贝来实现元素位置移动
    */
    fastRemove(es, index);
    return oldValue;
}

4.2、remove(Object o)

接下来看remove(Object o) 方法的源码

public boolean remove(Object o) {
	//获取当前数组
    final Object[] es = elementData;
	//获取当前数组大小
    final int size = this.size;
	//下面要使用i的值来查找当前o这个元素第一次在数组中出现的位置,默认从索引0处开始查找
    int i = 0;
    found: {
        // 如果要删除的元素是 null
        if (o == null) {
            //遍历列表
            for (; i < size; i++)
            // 如果找到了 null 元素
                if (es[i] == null)
                    //跳出found代码块
                    break found;
        } else {
            //遍历列表
            for (; i < size; i++)
                //找数组中谁和o的值相等
                if (o.equals(es[i]))
                    //如果找到了要删除的元素,跳出found代码块
                    break found;
        }
        return false;
    }
	//调用 fastRemove 方法快速删除元素
    fastRemove(es, i);
    return true;
}

该方法通过遍历的方式找到要删除的元素,null 的时候使用 == 操作符判断,非 null 的时候使用 equals() 方法,然后调用 fastRemove() 方法。
继续看fastRemove方法的源码

4.3、fastRemove

/**
 * 快速删除指定位置的元素。
 * @param index 要删除的元素的索引
 */
private void fastRemove(int index) {
    int numMoved = size - index - 1; // 计算需要移动的元素个数
    if (numMoved > 0) // 如果需要移动元素,就用 System.arraycopy 方法实现
        System.arraycopy(elementData, index+1, elementData, index,
                numMoved);
    elementData[--size] = null; // 将数组末尾的元素置为 null,让 GC 回收该元素占用的空间
}

5、查找元素-indexOf和lastIndexOf方法

5.1、indexOf

/**
 * 返回指定元素在列表中第一次出现的位置。
 * 如果列表不包含该元素,则返回 -1。
 * @param o 要查找的元素
 * @return 指定元素在列表中第一次出现的位置;如果列表不包含该元素,则返回 -1
 */
public int indexOf(Object o) {
	return indexOfRange(o, 0, size);
}

int indexOfRange(Object o, int start, int end) {
    Object[] es = elementData;
    // 如果要查找的元素是 null
    if (o == null) {
        //遍历列表
        for (int i = start; i < end; i++) {
            //如果找到了 null 元素
            if (es[i] == null) {
                // 返回元素的索引
                return i;
            }
        }
        // 如果要查找的元素不是 null
    } else {
        // 遍历列表
        for (int i = start; i < end; i++) {
            // 如果找到了要查找的元素
            if (o.equals(es[i])) {
                // 返回元素的索引
                return i;
            }
        }
    }
    // 如果找不到要查找的元素,则返回 -1
    return -1;
}

5.2、lastIndexOf

至于lastIndexOf,其源码实现就和indexOf很相像的,不同的是在遍历列表的时候一个是从头到尾一个是从尾巴开始到头;

public int lastIndexOf(Object o) {
    return lastIndexOfRange(o, 0, size);
}

int lastIndexOfRange(Object o, int start, int end) {
    Object[] es = elementData;
    if (o == null) {// 如果要查找的元素是 null
        for (int i = end - 1; i >= start; i--) {// 从后往前遍历列表
            if (es[i] == null) {// 如果找到了 null 元素
                return i;// 返回元素的索引
            }
        }
    } else {
        for (int i = end - 1; i >= start; i--) {// 从后往前遍历列表
            if (o.equals(es[i])) {如果找到了要查找的元素
                return i;// 返回元素的索引
            }
        }
    }
    return -1;// 如果找不到要查找的元素,则返回 -1
}

三、总结提升

ArrayList,又名动态数组,也就是可增长的数组,可调整大小的数组。动态数组克服了静态数组的限制,静态数组的容量是固定的,只能在首次创建的时候指定。而动态数组会随着元素的增加自动调整大小,更符合实际的开发需求。

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏哦。

你可能感兴趣的:(源码相关,ArrayList源码解读,源码,Java)