java 集合源码探究之ArrayList 扩容机制、底层实现原理

List接口的继承关系图

在这里插入图片描述

ArrayList

在这里插入图片描述

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

分析ArrayList的类定义,可以得到其实现了List接口, 以及继承了AbstractList类, 而AbstractList类又是 AbstractCollection的子类,AbstractCollection又实现了Collection接口 !

  • AbstractList 抽象类
  • Collection 接口
  • AbstractCollection 抽象类
  • List 接口

这就意味着 AbstractCollection 抽象类会实现 Collection 接口中的一部分方法!而它的子类AbstractList 又会重写一部分 AbstractCollection 实现了函数! 等到了ArrayList 这一层次, 对于一些集合的通用方法,它便可以直接使用AbstractList 中实现的方法,而不用全部实现List接口中的所有方法!

构造函数

new ArrayList<>()

构造函数, 构造出来一个空的集合,并且使用给定的大小初始化。可以得到以下信息

  • 可以指定大小创建ArrayList
  • 其底层是由数组实现
  • 具有默认的初始化大小
    /**
     * 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) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

添加方法

add

添加方法

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

ensureCapacityInternal

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

calculateCapacity

此方法可以判断当前集合是否第一次添加元素!第一次添加元素的话, 就会把当前需要添加元素的位置与默认的10比较, 返回其最大的!非首次添加元素的话, 就会直接返回当前值!得到的信息如下

  • ensureCapacityInternal(size + 1); 当前元素添加的位置

  • DEFAULT_CAPACITY = 10

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

ensureExplicitCapacity

上述方法的返回值将作为此函数的入参! 并且判断是否需要扩容 !

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

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

不扩容情况

此时回到 add方法中,我们执行到了 ensureCapacityInternal(size + 1); 然后把要添加的元素放到数组中可以了!

  • elementData 当前集合中的数组
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

扩容的情况

回到 ensureExplicitCapacity 方法中

grow(minCapacity)

增加容量,以确保至少可以容纳最小容量参数指定的元素数。

  • MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    /**
     * 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) {
        // 得到当前数组的长度
        int oldCapacity = elementData.length;
        // 扩容 1.5 倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 扩容1.5 倍之后还不能放下当前元素, 就直接扩容到当前位置!
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果扩容之后的大小, 比Integer.MAX_VALUE - 8;还要大! 就直接扩容到整形的最大值!
        // 否则就扩容到 Integer.MAX_VALUE - 8
        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

会判断当前元素要存放的位置是否大于, Integer.MAX_VALUE - 8 如果比它大, 就直接扩容到整形的最大值, 否则就扩容到Integer.MAX_VALUE - 8

  • minCapacity 当前元素要放到数组中的位置
  • MAX_ARRAY_SIZE : Integer.MAX_VALUE - 8;
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

elementData = Arrays.copyOf(elementData, newCapacity);

确定扩容的大小之后, 就开始使用 Arrays 直接复制。省略中间的过程,就是新建了个数组, 然后复制元素,最后把新的数组赋值给之前的elementData(数组)

    public static <T> T[] copyOf(T[] original, int newLength) {
        // 调用复制方法
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        // 会在判断类型之后, 创建一个数组的实例!
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

指定位置添加

add(int index, E element)

关键信息

  • System.arraycopy(elementData, index, elementData, index + 1,size - index);
    public void add(int index, E element) {
        // 检查范围
        rangeCheckForAdd(index);
		// 检查可扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // index位置后面的元素全部往后复制一个位置
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 添加当前位置的元素
        elementData[index] = element;
        // 修改数组大小
        size++;
    }

删除方法

首先会检查当前操作的位置在不在范围之内, 其次 改变modCount的值。得到需要移动元素的个数,将 index + 1以及后面的元素向前移动一位, 最后在把数组最后一个位置置为空!

主要方法

  • System.arraycopy(elementData, index+1, elementData, index, numMoved);
    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);
        // 最后在把数组最后一个位置置为空!
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

GET方法

  • 先检查范围
  • 直接返回当前位置的元素
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

SET方法

  • 检查范围
  • 直接覆盖当前位置的元素
    public E set(int index, E element) {
        rangeCheck(index);

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

找到元素的索引

如果当前元素是一个空值的话, 就直接在数组中找到为空的元素,直接返回。非空值的话, 就会使用equals 方法来判断对象是否相同!

  • 使用equals 方法来判断对象是否相同!
    public int indexOf(Object o) {
        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;
    }

测试扩容

public class demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        System.out.println(list.size());
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }
        System.out.println(list.size() + "- >" + getCapacity(list));
        list.add(12);
        list.add(13);
        System.out.println(list.size() + "- >" + getCapacity(list));
        list.add(Integer.MAX_VALUE - 9, 444);
        System.out.println(list.size() + "- >" + getCapacity(list));
    }

    /**
     * 使用反射来获得当前集合中的容量
     *
     * @param list
     * @return
     */
    public static Integer getCapacity(ArrayList list) {
        Integer length = null;
        Class c = list.getClass();
        Field f;
        try {
            f = c.getDeclaredField("elementData");
            f.setAccessible(true);
            Object[] o = (Object[]) f.get(list);
            length = o.length;
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return length;
    }

}

内部常用函数

rangeCheck

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

arraycopy

调用本地方法实现数组的复制操作,参数信息如下:

  • @param src 当前的资源数组
  • @param srcPos 资源数组中开始位置
  • @param dest 资源数组的目的位置
  • @param destPos 目的资源(数组)开始的位置
     * @param      src      the source array. // 当前的资源数组
     * @param      srcPos   starting position in the source array.// 资源数组中开始位置
     * @param      dest     the destination array.// 资源数组的目的位置
     * @param      destPos  starting position in the destination data.// 目的资源(数组)开始的位置
     * @param      length   the number of array elements to be copied.// 将要被复制元素的数量
     */
    public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length);

小总结

  • arraylist 默认的容量大小是 10, 即使里面一个元素都没有。
  • 当要数组中的元素比容量大的时候, 会选择先扩容后插入元素。
  • 每次扩容都会在原来的基础之上增加 0.5 倍。
  • 扩容时候会新建立一个数组, 然后把元素复制过去。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值