ArrayList源码解析

本文深入解析了ArrayList的底层实现,包括其默认容量、数组初始化、扩容机制、元素添加、移除和获取等关键操作的实现原理。
一定要耐心看完,会有收获的!

**ArrayList底层属性**
                                                                                                                                                                                                                                                                                                      //默认容量10
private static final int DEFAULT_CAPACITY = 10;

//用于空实例的数组对象
private static final Object[] EMPTY_ELEMENTDATA = {};

//初始化默认大小的数组对象,和空数组实例区分开来
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//存储ArrayList元素的数组
transient Object[] elementData; 

//ArrayList的大小(它包含元素的数量)
private int size;

//数组分配最大的容量,因为虚拟机在数组中保留一些头信息,尝试分配最大的数组长度,可能会导致OutOfMemoryError:
//请求的数组大小超过VM的限制
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

**ArrayList初始化(3种方式)**

/**
 * (1)构造指定初始化容量的空列表
 * initialCapacity(初始化数组容量)
 */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        //如果初始化容量大于0,就对elementData进行初始化,并指定数组长度 
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        //若果初始化容量等于0,就表示是一个长度为0的空数组,用空数组对象EMPTY_ELEMENTDATA来初始化 
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        //小于0的话就报错
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

/**
 * (2)初始化数组,不指定长度,用默认的数组对象DEFAULTCAPACITY_EMPTY_ELEMENTDATA来初始化
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * (3)构造一个包含指定元素的数组
 */
public ArrayList(Collection<? extends E> c) {
    //初始化elementData
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        if (elementData.getClass() != Object[].class)
            //size等于elementData的长度,如果不等于0并且elementData的类型不是Object类型,
            //Arrays.copyOf扩容操作生成新的数组
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //size等于0时,用空数组EMPTY_ELEMENTDATA初始化对象
        this.elementData = EMPTY_ELEMENTDATA;
    }
}


**ArrayList底层数组扩容**

/**
 * 将元素添加到列表末尾
 */
public boolean add(E e) {
    //扩容操作,调用ensureCapacityInternal方法传参当前数组大小加1,表示新增一个元素,数组需要的最小容量
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //扩容之后将新增元素追加到集合末尾
    elementData[size++] = e;
    return true;
}

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

/**
 * 计算数组需要的容量
 * elementData(元素数组),minCapacity(数组需要的最小容量)
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //如果数组等于默认数组实例,表示数组第一次添加元素,需要进行初始化数组长度
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //默认的数组长度DEFAULT_CAPACITY(10)和最小需要的数组长度作比较,谁大取谁,保证数组容量足够容纳元素
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //否则返回数组需要最小的长度
    return minCapacity;
}

/**
 * 判断数组是否需要扩容
 */
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 如果数组需要最小的长度大于存储元素的数组的长度,就调用扩容方法
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/**
 * 数组扩容
 * minCapacity(数组需要最小的长度),MAX_ARRAY_SIZE(数组能分配最大的容量)
 * oldCapacity(添加新元素之前的数组长度)
 */
private void grow(int minCapacity) {
    // 老数组的长度(添加元素之前数组的长度)
    int oldCapacity = elementData.length;
    // 老数组的长度*1.5,生成新数组的长度(老数组长度的*二进制右移1位再转成10进制,相当于老数组的0.5倍)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        //如果扩容后的新数组长度小于数组需要最小的长度,那么新数组长度就等于数组需要最小的长度
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        //如果新数组的长度大于数组分配最大的容量,调用hugeCapacity方法
        newCapacity = hugeCapacity(minCapacity);
    //最后拿数组和扩容后的长度生成新的数组,就是数组扩容
    elementData = Arrays.copyOf(elementData, newCapacity);
}

/**
 * 数组能分配的最大容量
 * minCapacit(数组需要最小的长度),MAX_ARRAY_SIZE(数组能分配最大的容量)
 */
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        //若果数组需要最小的长度小于0,就抛异常
        throw new OutOfMemoryError();
    //如果数组需要最小的长度大于数组能分配的最大容量,就返回Integer最大值,否则返回MAX_ARRAY_SIZE
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

/**
* 判断索引是否在数组范围内,如果没有就抛异常
*/
private void rangeCheck(int index) {
	if (index >= size)
	throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}


** 移除指定下标的元素 **

public E remove(int index) {
	//先判断下标是否在数组范围内
    rangeCheck(index);

    modCount++;
    //保存要移除的元素,当做remove的返回值
    E oldValue = elementData(index);

	//要移除的元素后边剩余元素的长度
    int numMoved = size - index - 1;
    //如果大于0说明要移除元素不是最后一个元素
    if (numMoved > 0)
    	//将要移除元素后边剩余的元素,从要移除元素的位置开始,依次向前移位,相当于把要移除的元素给覆盖了从而达到删除效果
        System.arraycopy(elementData, index+1, elementData, index,
                           numMoved);
    //移除完元素后,数组的长度减1                   
    elementData[--size] = null;
    
	//返回移除的元素对象
    return oldValue;
}


 ** 根据下标获取元素 **

public E get(int index) {
	//判断下标是否在正确的范围内
    rangeCheck(index);
	
    return elementData(index);
}

//索引元素返回指定下标的值
@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}

System.arraycopy是一个静态的本地方法,由虚拟机实现,效率比用java一个一个复制高
底层方法:public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos,  int length);
从原数组src取元素,范围为srcPos开始到srcPos+length-1,取出length个元素,存放到目标数组中,
存放的下标为destPos到destPos+length-1;

Arrays.copyOf底层还是System.arraycopy,只是把原数组从下标0开始移到新数组中,并且指定长度,底层方法如下:
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;
}

从上面移除、获取元素可以看出,ArrayList移除元素时需要对数组元素进行移位remove(Object obj)时间复杂度为O(n),
获取元素根据其下标索引元素get(int i)时间复杂度为O(1);
    
结束语:如果有写的不好或者不太懂得地方可以在下方评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值