多图深度解析ArrayList源码

本文详细解析了ArrayList的内部实现,包括其特性、存储结构、使用示例、类关系、扩容机制以及常见操作。重点讨论了如何在添加元素时自动扩容,以及在开发中如何有效地利用ArrayList提高性能。同时,文章介绍了ArrayList的Fail-Fast机制,确保在并发修改时的正确性。
摘要由CSDN通过智能技术生成

1. 概述

ArrayList 可以说是实际开发过程中使用最多的数据结构,我们有必要深究其原理,以便更好的提升它的效率。

1.1 特性

  • 顺序容器:写入和读出的顺序是一致的
  • 允许存储重复值,允许你存储null值
  • 容量不足时,容器会自动增大底层数组的大小

1.2 存储结构

在这里插入图片描述

2. 使用

2.1 代码

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.Arrays;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ArrayListTest {
    @Test
    public void test() {
        ArrayList<Integer> arr = new ArrayList<>();
		// size()获取到的是元素的个数
        System.out.println("默认大小: " + arr.size());

        arr.add(1);
        arr.add(2);
        arr.add(1);
        arr.addAll(Arrays.asList(5, 6));

        System.out.println("写入之后的大小: " + arr.size());
    }
}

2.2 输出

默认大小: 0
写入之后的大小: 5

3. 类关系

3.1 UML类图

在这里插入图片描述

4. ArrayList的实现

4.1 类属性

	/**
     * 初始默认数组容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 多个空 ArrayList实例之间共享的 空数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 将其与 EMPTY_ELEMENTDATA 区分开来,以便知道添加第一个元素时扩充多少
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 当 elementData  添加了第一个元素之后,容量就会被扩充到 DEFAULT_CAPACITY (10)个
 	 *
	 *	transient作用:不让elementData被序列化
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList 的大小(实际存储的元素个数)
     */
    private int size;

4.2 构造方法

	/**
     * 指定容量
     *
     */
    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);
        }
    }

    /**
     * 默认容量为10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 传入集合,可初始化元素
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 使用空数组代替
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

5. 扩容机制

5.1 自动扩容

有一下两要点:

  • 每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容
  • 在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量

使用时候注意:

  1. 当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量
  2. 根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
	/**
     * @param   minCapacity   期望的最小容量
     */
    public void ensureCapacity(int minCapacity) {
    	// 从当前的元素中,计算最小的扩展个数
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // 不是空数组(说明已经有元素了),就先不扩展
            ? 0
            // 元素个数为10
            : DEFAULT_CAPACITY;
		// 要扩展的容量多于最小扩展量才进行扩展
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
	
	/**
	* 计算最小容量
	*/
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

	/**
	* 当最小容量大于当前元素大小时,进行扩容
	*/
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * ArrayList的最多元素个数  2147483639
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
	 * 扩充容量,确保数组能够至少可存 minCapacity 个元素
     */
    private void grow(int minCapacity) {
        // 数组原始个数
        int oldCapacity = elementData.length;
        /**
        * 比如原始 oldCapacity == 10(二进制为 1010)
        * 1010 >> 1 等价于 0101 (十进制为5);PS: 整数进行位右移相当于除以2
        * 那么新的容量就是 10 + 5 = 15;相当于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);
    }

	/**
	* 确保最大容量也只能是 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;
    }

以上逻辑可以展示为如图所示:
在这里插入图片描述

5.2 添加单个元素

  • add(E e)
  • add(int index, E element)

这两个方法都是向容器中添加新元素,这可能会导致capacity不足,因此在添加元素之前,都需要进行剩余空间检查,如果需要则自动扩容。扩容操作最终是通过grow()方法完成的。

5.2.1 追加 (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!!
        elementData[size++] = e;
        return true;
    }

modCount
表明list被结构化修改的次数,存在于 java.util.AbstractList 中,定义方式:protected transient int modCount = 0;, 该字段由迭代器和列表迭代器实现使用,提供 fail-fast 机制。

结构化修改的形式:

  • 指改变列表的大小。
  • 以某种方式干扰列表,使得正在进行的迭代可能产生不正确的结果。

在这里插入图片描述

5.2.2 追加到指定索引位置

此种方式需要对索引位置之后的元素进行移动。

/**
     * 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) {
    	// 校验 index > size || index < 0,防止越界
        rangeCheckForAdd(index);
		
		
        ensureCapacityInternal(size + 1);  // 增加 modCount!!
        // 从指定的源数组复制数组,从指定位置到目标数组的指定位置
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

在这里插入图片描述

5.3 添加多个元素

在插入之前也需要进行空间检查,如果需要则自动扩容;如果从指定位置插入,也会存在移动元素的情况。 addAll()的时间复杂度不仅跟插入元素的多少有关,也跟插入的位置相关。

addAll(Collection<? extends E> c)

 /**
     * 如果在操作过程中修改了指定集合,则此操作的行为未定义。
     * (这意味着如果指定的集合是该列表,并且该列表是非空的,则该调用的行为是未定义的
     *
     * @param 待加入的集合
     * @return list改变之后返回 true
     * @throws 集合未定义则抛出 NullPointerException
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        // 要添加的长度
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // 增加 modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

addAll(int index, Collection<? extends E> c)

	/**
     *
     * @param 插入位置
     * @param 待添加的集合
     * @return list改变之后返回 true
     * @throws index 越界则抛出 IndexOutOfBoundsException
     * @throws 集合未定义则抛出 NullPointerException
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
		// 要移动的数量
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

5.4 赋值/更改 set()

/**
     * 替换指定索引处的值
     *
     * @param index指定要改哪个值
     * @param 元素值
     */
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

5.5 获取指定索引处的值

    public E get(int index) {
    	// 判断 index >= size
        rangeCheck(index);

        return elementData(index);
    }

5.6 删除指定索引处的值

 	/**
     * 删除该值后,元素左移动
     */
    public E remove(int index) {
        rangeCheck(index);
		
		// 删除也增加更改次数
        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // 将最后一个元素设置为null,让GC生效

        return oldValue;
    }

有了垃圾收集器并不意味着一定不会有内存泄漏。对象能否被GC的依据是是否还有引用指向它,上面代码中如果不手动赋null值,除非对应的位置被其他元素覆盖,否则原来的对象就一直不会被回收。

5.7 调整容量为当前数组大小

	/**
     *最小化ArrayList的容量
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

5.8 查找元素

indexOf()

/**
     * 查找元素第一次出现的位置
     * 
     */
    public int indexOf(Object o) {
    	// ArrayList 可以存null值,所以要判断
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
        	// 从头开始找
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

lastIndexOf()

/**
     * 查找元素最后一次出现的位置
     */
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
        	// 从尾开始找
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

6. Fail-Fast机制

ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

参考文档:
https://pdai.tech/md/java/collection/java-collection-ArrayList.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值