【源码学习】ArrayList源码初探(JDK8)

概括

动态数组、线程不安全,允许为空

一、类结构

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

ArrayList实现了List、RandomAccess、Cloneable、Serializable接口,其中RandomAccess表示快速随机访问,Cloneable表示可克隆,Serializable表示可序列化。

二、属性

//序列化ID
private static final long serialVersionUID = 8683452581122892189L;
//默认初始扩容大小
private static final int DEFAULT_CAPACITY = 10;
//空数组对象,当ArrayList(int initialCapacity)传参为零时赋值给elementData
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组对象,当ArrayList()时赋值给传参为零时赋值给elementData
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//底层存放数据的地方,可见ArrayList底层是数组
transient Object[] elementData;
//当前数组长度
private int size;
//数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

DEFAULTCAPACITY_EMPTY_ELEMENTDATADEFAULT_CAPACITY的区别:

源码中这样表述的:We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.

翻译为与EMPTY_ELEMENTDATA 的区别在于清楚第一次添加时扩容大小,有待理解

三、构造方法

1、无参构造

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

使用无参构造,则elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,大小为0,在第一次add时进行扩容,后面会讲

2、带int类型参数构造方法

 public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //给定大小大于零则创建给定大小的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //等于零时引用EMPTY_ELEMENTDATA空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

给定初始容量大小,在第一次add时判断是否进行扩容,此处疑惑:当传入合法参数时,代码跟入该构造方法后initialCapacity始终为1?

3、带Collection参数构造方法

public ArrayList(Collection<? extends E> c) {
    	//集合转数组
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // 不同Collection的toArray()实现不一样,因此此处需要判断返回类型重新构造
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //如果长度为空,则替换为EMPTY_ELEMENTDATA空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    	//判断创建类型进行直接构造或者反射实例
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    	//调用本地方法System.arraycopy拷贝
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

四、常用API

1、add函数

添加单个元素

public boolean add(E e) {
    	//扩容,默认构造方法创建后在第一次add时首次扩容至10
        ensureCapacityInternal(size + 1);
    	//放置元素
        elementData[size++] = e;
        return true;
 }

扩容方法ensureCapacityInternal代码如下:

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

//如果是空数组,第一次扩容至默认大小10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
 }

 private void ensureExplicitCapacity(int minCapacity) {
     	//add会修改操作数
        modCount++;
     	//如果当前元素个数+1超出数组容量则进行扩容
        if (minCapacity - elementData.length > 0)
            //扩容方法
            grow(minCapacity);
 }

private void grow(int minCapacity) { 
        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);
        //使用copy进行扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
}

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;
}

解析:

​ a、如果初始是无参构造方法创建的空数组,第一次add时扩容至DEFAULT_CAPACITY(10)大小

​ b、后续add操作则每次minCapacity为增加后的元素个数,判断如果minCapacity超过当前数组容量,则进行扩容

if (minCapacity - elementData.length > 0)

​ c、每次扩容大小为原来的1.5倍,采用位运算计算int newCapacity = oldCapacity + (oldCapacity >> 1);

​ d、Arrays.copyOf()方法底层调用了本地方法System.arraycopy(original, 0, copy, 0,                          Math.min(original.length, newLength) )

指定位置插入元素

public void add(int index, E element) {
    	//首先判断插入下标是否越界
        rangeCheckForAdd(index);
		//判断扩容
        ensureCapacityInternal(size + 1); 
    	//从插入位置整体向后移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
    	//插入元素替换下标元素
        elementData[index] = element;
    	//元素个数+1
        size++;
}
//判断下标是否越界
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

解析:

​ a、插入前首先判断下标是否越界

​ b、判断是否需要扩容

​ c、从下标位置开始使用复制方法整体向后移位,然后替换下标位置为插入元素

​ d、因为涉及大量元素移位,所以插入效率低

批量插入

public boolean addAll(Collection<? extends E> c) {
    //将集合转为数组
    Object[] a = c.toArray();
    int numNew = a.length;
    //判断是否需要扩容
    ensureCapacityInternal(size + numNew); 
    将数组a复制到list数组中
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

指定位置批量插入

public boolean addAll(int index, Collection<? extends E> c) {
	//判断下标是否越界
    rangeCheckForAdd(index);
	//将集合转为数组
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);

    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;
}

2、移除元素

按元素删除

public boolean remove(Object o) {
    //判断传入是否为null
    if (o == null) {
        //遍历元素,可删除位于首位和中间为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;
}
//删除元素,涉及到删除位以后元素整体位移
private void fastRemove(int index) {
    	//操作数+1
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; 
}

按下表删除

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;
	//返回删除的元素
    return oldValue;
}

解析:

​ a、按元素删除可删除首位和中间位置为null的元素

​ b、按元素删除返回值为true/false,按下标删除返回值为删除的元素

​ c、删除也涉及元素位移,比较耗时

3、查找元素

public E get(int index) {
    //判断下标是否越界
    rangeCheck(index);
    return elementData(index);
}

​ 没啥好说的

4、修改元素

public E set(int index, E element) {
    //凡是涉及下标的都会首行校验下标
    rangeCheck(index);
    E oldValue = elementData(index);
    //替换为新元素
    elementData[index] = element;
    //返回旧元素
    return oldValue;
}

​ 也没啥好说的,凡是涉及下标都要进行下标越界校验

五、总结

​ Arraylist基于数组实现,随机访问和遍历效率高,插入和删除涉及元素位移,效率比较低;扩容方式为第一次到10,以后超出容量则扩容至1.5倍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值