深入剖析ArrayList的底层源码,再也不怕面试官问了!

写在前面: 我是「扬帆向海」,这个昵称来源于我的名字以及女朋友的名字。我热爱技术、热爱开源、热爱编程。技术是开源的、知识是共享的

这博客是对自己学习的一点点总结及记录,如果您对 Java算法 感兴趣,可以关注我的动态,我们一起学习。

用知识改变命运,让我们的家人过上更好的生活

相关文章:

深入剖析LinkedList的底层源码

ArrayList 与 LinkedList 的方法及其区别


一、ArrayList介绍及其源码剖析

Resizable-array implementation of the List interface. Implements all
optional list operations, and permits all elements, including null. In
addition to implementing the List interface,this class provides
methods to manipulate the size of the array that is used internally to
store the list. (This class is roughly equivalent to Vector, except
that it is unsynchronized.)

  1. ArrayList 是从JDK1.2 引入的。

  2. ArrayList是可调整大小的数组,实现了List接口。 实现所有可选列表操作,并允许所有元素包括null 。 除了实现List 接口之外,该类还提供了一些方法来操纵内部使用的存储列表的数组的大小。 (这个类是大致相当于Vector,不同之处在于它是不同步的)。

  3. ArrayList内部封装一个数组,用数组来存储数据。内部数组的默认初始容量 10,存满后 1.5 倍增长

  4. 从 JDK1.8开始, ArrayList 一开始创建一个长度为 0 的数组,当添加第一个元素时再创建一个始容量为 10 的 数组。

继承结构

在这里插入图片描述

  • ArrayList继承于AbstractList,实现了Cloneable、List、RandomAccess、Serializable这些接口。
  • ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
  • ArrayList 实现了Cloneable接口,即覆盖了函数clone(),所以它能被克隆。
  • ArrayList 实现了RandmoAccess接口,因此提供了随机访问功能。
  • ArrayList 实现了Serializable接口,这意味着ArrayList支持序列化

ArrayList 属性源码剖析

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
	// 序列化 id
    private static final long serialVersionUID = 8683452581122892189L;

    // 默认容量的大小为 10
    private static final int DEFAULT_CAPACITY = 10;

    // 空实例共享的一个空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 默认的空数组常量为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // transient不可序列化, 存放元素的数组
    transient Object[] elementData; // non-private to simplify nested class access

   // 元素的大小,默认是0
    private int size;

    ......
	
	// 最大数组容量为 Integer.MAX_VALUE – 8
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

注意:被标记为transient的属性在对象被序列化的时候不会被保存,也就是它不会被序列化。

二、构造方法及其源码剖析

1. 带int类型的构造方法

源码剖析

/**
 * Constructs an empty list with the specified initial capacity.
 *
 * @param  initialCapacity  the initial capacity of the list
 * @throws IllegalArgumentException if the specified initial capacity
 *         is negative
 */
public ArrayList(int initialCapacity) {
	// 如果初始容量大于0
    if (initialCapacity > 0) {
    	// 新建一个初始容量大小的数组
        this.elementData = new Object[initialCapacity];
     // 如果初始容量为0
    } else if (initialCapacity == 0) {
    	// 将EMPTY_ELEMENTDATA赋值给 elementData
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
    	// 否则初始容量小于0,则抛出异常 “非法的容量”
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

总结

  • 如果传入参数也就是初始容量大于0,则新建一个初始容量大小的数组;
  • 如果传入的参数初始容量等于0,将EMPTY_ELEMENTDATA赋值给 elementData;
  • 如果传入的参数初始容量小于0,将抛出异常。

2. 无参构造方法

源码剖析

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
	// 没有指定初始容量,将成员变量elementData的值设为默认值
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

总结

  • 如果没有传入参数,则使用默认无参构建方法创建ArrayList对象。elementData是一个大小为0的空数组。

3. Collection<? extends E>型构造方法

源码剖析

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @param c the collection whose elements are to be placed into this list
 * @throws NullPointerException if the specified collection is null
 */
public ArrayList(Collection<? extends E> c) {
	// 将参数中的集合转换为数组,赋值给 elementData 
    elementData = c.toArray();
    // 如果数组的大小不等于 0
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        // 如果c.toArray()返回的数组类型不是Object[]类型的
        // 则利用Arrays.copyOf()创建一个大小为size的、类型为Object[]的数组, 并将elementData中的元素复制到新的数组中
        // 最后让elementData 指向新的数组
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else { // 否则设置元素数组为空数组
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

总结

  • 将参数中的集合转换为数组,赋值给 elementData
  • .给size进行赋值,size代表集合元素数量。判断参数是否为空,如果数组的大小不等于 0,则进一步判断是否转化为Object类型的数组,如果不是,则进行复制;
  • 否则,设置元素数组为空数组

三、常用方法及其源码剖析

1. get()方法

get(int index)方法是返回集合中指定位置的元素。

源码剖析

/**
 * Returns the element at the specified position in this list.
 *
 * @param  index index of the element to return
 * @return the element at the specified position in this list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
	// 检查下标索引是否越界
    rangeCheck(index);

	// 返回指定索引处的值
    return elementData(index);
}
  • 首先会检查索引值是否越界,如果索引值大于数组大小,则会抛出异常。

源码如下:

private void rangeCheck(int index) {
	// 索引值大于数组大小
    if (index >= size)
    	// 抛出异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
  • 如果所引是合法的,则调用elementData(int index)方法获取指定位置的元素。

2. set() 方法

set(int index, E element) 方法将index索引处的元素替换成element对象,返回被替换的旧元素。

源码剖析

/**
 * Replaces the element at the specified position in this list with
 * the specified element.
 *
 * @param index index of the element to replace
 * @param element element to be stored at the specified position
 * @return the element previously at the specified position
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E set(int index, E element) {
	// 检查下标索引是否越界
    rangeCheck(index);
	
	// 指定下标处的旧值
    E oldValue = elementData(index);
    // 指定下标处赋新的值
    elementData[index] = element;
    // 返回旧值
    return oldValue;
}

3. add() 方法

  • add(E e) 方法 将指定的元素追加到此集合的末尾

源码剖析

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
	// 插入元素之前,会检查是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将元素添加到数组中最后一个元素的后面,最后将集合中实际的元素个数加 1
    elementData[size++] = e;
    return true;
}
  • add(int index, E element) 在集合元素序列 index 位置处插入

源码剖析

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @param index index at which the specified element is to be inserted
 * @param element element to be inserted
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
	// 检查下标索引是否越界
    rangeCheckForAdd(index);

	// 插入元素之前,会检查是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将 index 位置及其后面的所有元素都向后移一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 将新元素插入到 index 位置处,元素个数加 1
    elementData[index] = element;
    size++;
}

4. remove() 方法

  • remove(int index) 方法是删除该集合中指定位置的元素

源码剖析

/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).
 *
 * @param index the index of the element to be removed
 * @return the element that was removed from the list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
 public E remove(int index) {
 		// 检查下标索引是否越界
        rangeCheck(index);

        modCount++;
        // 返回被删除的元素值
        E oldValue = elementData(index);
		
		// 需要移动的元素的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	// 将 index + 1 及其后面的元素向前移动一位,覆盖被删除的元素值
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 把数组最后一个元素赋值为null,将元素个数减一
        elementData[--size] = null; // clear to let GC do its work
		// 返回旧值
        return oldValue;
    }
  • remove(Object o) 方法是删除指定的元素,如果有重复的元素,则只会删除下标最小的元素

源码剖析

 /**
  * Removes the first occurrence of the specified element from this list,
  * if it is present.  If the list does not contain the element, it is
  * unchanged.  More formally, removes the element with the lowest index
  * <tt>i</tt> such that
  * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
  * (if such an element exists).  Returns <tt>true</tt> if this list
  * contained the specified element (or equivalently, if this list
  * changed as a result of the call).
  *
  * @param o element to be removed from this list, if present
  * @return <tt>true</tt> if this list contained the specified element
  */
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
    	// 遍历数组,查找要进行删除元素的位置
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
  • fastRemove(int index) 方法是快速删除,删除跳过边界检查的方法,也不返回删除的元素值

源码剖析

/*
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
private void fastRemove(int index) {
	   modCount++;
	   int numMoved = size - index - 1;
	   if (numMoved > 0)
	       System.arraycopy(elementData, index+1, elementData, index,
	                        numMoved);
	   elementData[--size] = null; // clear to let GC do its work
}

5. size() 方法

返回集合中的元素个数

源码剖析

/**
 * Returns the number of elements in this list.
 *
 * @return the number of elements in this list
 */
public int size() {
    return size;
}

6. indexOf(Object o) 方法

返回对象o在集合中第一次出现的位置的索引

源码剖析

/**
 * Returns the index of the first occurrence of the specified element
 * in this list, or -1 if this list does not contain the element.
 * More formally, returns the lowest index <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
public int indexOf(Object o) {
		// 如果查找的元素为空
        if (o == null) {
        	// 循环遍历数组
            for (int i = 0; i < size; i++)
            	// 如果 i 位置的原素为空 
                if (elementData[i]==null)
                	// 返回下标
                    return i;
        // 否则,查找的元素不为空
        } else {
            // 循环遍历数组
            for (int i = 0; i < size; i++)
            	// 找到第一个和要查找的元素相等的元素
                if (o.equals(elementData[i]))
                	// 返回下标
                    return i;
        }
        // 返回 -1.则表示没有找到
        return -1;
    }

四、ArrayList 和 Vector 的区别

  • ArrayList 和 Vector 底层都是 数组
  • ArrayList 每次扩容的情况下扩容为原来的1.5 倍。线程不安全,当多个线程同时访问同一个ArrayList 集合时,如果两个或两个以上的线程修改了 ArrayList 集合,则必须手动保证该集合的同步性。
  • Vector 是同步类,其线程安全,但是它的访问比较慢。Vector 每次扩容为其空间大小的 2 倍。

五、对扩容原理源码剖析及其思考

  1. 在add()方法中调用ensureCapacityInternal()方法,用来确保添加元素的时候,最小容量是 minCapacity。

源码剖析

private void ensureCapacityInternal(int minCapacity) {
	// 判断元素数组是否为空数组
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    	// 取最大值
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}
  1. 在ensureCapacityInternal()方法里面调用ensureExplicitCapacity(int minCapacity)方法,用来判段集合在添加元素的时候是否需要对当前的数组进行扩容。

源码剖析

private void ensureExplicitCapacity(int minCapacity) {
   modCount++;

    // overflow-conscious code
    // 判断minCapacity与当前元素数组的长度的大小
    // 如果minCapacity比当前元素数组的长度的大小大的时候需要扩容
    if (minCapacity - elementData.length > 0)
    	// 扩容
        grow(minCapacity);
}
  1. 在ensureExplicitCapacity() 方法里面调用grow(int minCapacity)方法,进行扩容。

源码剖析

 /**
  * Increases the capacity to ensure that it can hold at least the
  * number of elements specified by the minimum capacity argument.
  *
  * @param minCapacity the desired minimum capacity
  */
private void grow(int minCapacity) {
    // overflow-conscious code
    // 原本的容量
    int oldCapacity = elementData.length;
    // 新的容量是原本容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新容量小于指定的容量
    if (newCapacity - minCapacity < 0)
    	// 将指定的容量赋值给新容量
        newCapacity = minCapacity;
    // 如果新容量大于指定的容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    	// 指定新的数组容量
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    // 将旧数组拷贝到扩容后的新数组中
    elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
 *hugeCapacity()方法,用于处理minCapacity超出MAX_ARRAY_SIZE的情况
 */
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
  • 最后调用 Arrays.copyOf()方法,用来复制原数组,将 elementData 赋值为得到的新数组。

注意

由于数组复制的代价比较大,因此建议在创建 ArrayList 对象的时候就指定大概的容量大小,从而减少扩容操作的次数

六、ArrayList的优缺点

1. 优点

  • 能够自动扩容,默认每次扩容为原来容量大小的1.5倍。
  • 根据下标遍历元素,效率高。
  • 根据下标访问元素,效率高。

2. 缺点

  • 线程不安全。

  • 在中间插入或删除元素的时候需要移动元素,效率低。


由于水平有限,本博客难免有不足,恳请各位大佬不吝赐教!

相关推荐
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页