ArrayList源码分析-超详细

分析方式:断点调试,一步一步跟代码。(基于openjdk1.8)

先看看ArrayList类定义的一些私有属性

private static final int DEFAULT_CAPACITY = 10;//数组默认大小
private static final Object[] EMPTY_ELEMENTDATA = {};//空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//空数组(这两个空数组有啥区别,后面说)
transient Object[] elementData;//存放list内容的数组  被transient修饰,内容不会被序列化
private int size;//具体的list内容大小  (和数组的大小是有区别的)

1.我们在使用ArrayList的时候,第一步肯定是new
调用ArrayList的构造方法,如下,有三个构造方法

	//无参构造方法,将elementData赋值为空数组{}
 	public ArrayList() {
 		// Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
	
	//接受参数为elementData的大小
	public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//大于0时直接new一个对应大小的object数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {//等于0时,赋值为空数组
        	// Object[] EMPTY_ELEMENTDATA = {}
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

	 public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//将list转为数组,赋值给elementData
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
            	//如果参数数组类型不是Object,就转为Object类型,重新赋值给elementData
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 如果参数List大小为0,将elementData 赋值为{}
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 两个都表示空数组{ },区别就是当你调用无参构造函数时用的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,当你传了数组的初始化大小参数,但是值为0时用的EMPTY_ELEMENTDATA。
可以看出ArrayList在没有指定初始化大小时,往里面添加元素才会初始化内部的数组。
2.接下来看list.add()方法,如下,有两个重载方法

	public boolean add(E e) {
		//检查当前elementData的长度,如果已经小于size+1,就需要扩容
		//需要区别size和elementData长度
		//size表示list内容大小;elementDeata表示存放内容的数组长度
		//也就是说elementDeata的长度必须大于size+1,才能放新的内容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将内容放入数组末尾
        elementData[size++] = e;
        return true;
    }

	public void add(int index, E element) {
		//检查index下标是否在[0,size]之间
        rangeCheckForAdd(index);
		//检查elementData还能不能存入内容
		//不能就会扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将index下标后面的内容后移(比如要将内容放到index=2的位置,那么以前index>=2的内容都需要往后移一位)
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

由上,下面我们看看ensureCapacityInternal(size + 1)和System.arraycopy这两个方法的实现

先看ensureCapacityInternal(size + 1)方法,这个方法也是ArrayList扩容方法

	private void ensureCapacityInternal(int minCapacity) {
		//先是调用了 calculateCapacity
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
	
	private static int calculateCapacity(Object[] elementData, int minCapacity) {
		//当你在new ArrayList时没传参数或者参数为0,第一次往list里面add内容之前,elementData={}
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	//取两个值,较大的那个
        	//int DEFAULT_CAPACITY = 10
        	//这里是添加单个元素minCapacity=1,所以会返回DEFAULT_CAPACITY为10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
	//这时的minCapacity是上个方法返回值10
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        //如果是第一次添加元素,这里minCapacity=10  elementData.length=0
        //或者 如果数组中已经存放不下内容时,就会发生数组扩容
        //具体的扩容逻辑看grow(minCapacity)方法
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

	private void grow(int minCapacity) {
        // overflow-conscious code
        //得到久的数组长度
        int oldCapacity = elementData.length;
        //(oldCapacity >> 1):将oldCapacity化为二进制,再右移一位
        //比如oldCapacity=5,5化为二进制为00000101;右移一位就是00000010,化为10进制就是2(简单来说就是除2取整)
        //将新数组newCapacity = oldCapacity + oldCapacity * 0.5
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //minCapacity=10
        //如果小于10,newCapacity=10
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
            //MAX_ARRAY_SIZE = 2147483639
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //将elementData拷贝到新的elementData[newCapacity]中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

	//下面我们看看Arrays.copyOf()方法
	 @SuppressWarnings("unchecked")
    public static <T> T[] copyOf(T[] original, int newLength) {
    	//需要记住这三个参数分别是啥,不然后面会搞混
    	//original:原始扩容前存放内容的elementData数组
    	//newLength:需要扩容到的数组大小
    	//original:原始数组的Class
        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)//如果是Object类型数组
            ? (T[]) new Object[newLength]//new Object[]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);//创建其他类型数组
         //创建了新数组,下一步就是将原始数组original中的数组拷贝到新数组中
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

下面我们开始看System.arraycopy()方法

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

这个方法被native修饰,是一个本地方法(简单来说就是Java定义接口,由c++来实现,为什么要c++实现呢?肯定是因为用c++来实现效率更高啥),具体C++怎么实现了,大家有兴趣可以自己去看看。
我们主要来看这五个参数分别是什么意思,下面说个实例来说明:

public class TestArrayList {
	public static void main(String[] args) {
		Object[] original = {1,3,5,7,9};
		Object[] dest = new Object[10];
		testCopy(original, dest);
		System.out.println(Arrays.toString(dest));
	}
	//将original拷贝到dest数组
	public static void testCopy(Object[]original,Object[]dest) {
		System.arraycopy(original, 0, dest, 0, 2);
	}
}

控制台输出结果为:

[1, 3, null, null, null, null, null, null, null, null]

System.arraycopy(original, 0, dest, 0, 2)这五个参数的意思分别是:
1.源数组
2.从源数组的那个位置开始拷贝
3.目标数组
4.从目标数组的那个位置开始存放
5.从源数组拷贝几个。
大家可以将这个类拷贝到自己本地,修改参数印证一下。

对上诉的一些关键点,不知道大家get到没有,做个总结:

  1. 往空ArrayLIst(没有指定初始大小)中添加数据,存放数据的数组elementData的大小会变为10。(也就是说第一次扩容发生在往集合添加第11个元素时)
  2. 当数据量大时,应尽量避免调用add(int index, E element),尤其是循环体中,因为每一次添加都要调用System.arraycopy()方法
  3. 当我们数据量大时,可以预估一个初始list大小,调用 public ArrayList(int initialCapacity)构造方法传入初始值,避免引发频繁的数组扩容
  4. 数组的每次扩容是变为老数组的1.5倍大小(newCapacity = oldCapacity + (oldCapacity >> 1))
  5. add方法在每次具体往数组放内容前,都会判断存放内容的数组elementData能不能装下内容,如果size>elementData.length就需要扩容,扩容的方式是先创建新数组,然后调用System.arraycopy()(是一个本地方法,最终是由c++实现)方法将elementData中的内容拷贝到新数组中,最后重复赋值给elementData。

下面我们来看其他的一些常用方法的源码:
list.remove(index)

 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; // 将size位置内容设置为null
        return oldValue;//返回旧值
    }

list.set(index,element)

public E set(int index, E element) {
        rangeCheck(index);//检查索引范围

        E oldValue = elementData(index);
        elementData[index] = element;//将数组对应下标设置为新值
        return oldValue;
    }

list.get(index);

 public E get(int index) {
        rangeCheck(index);

        return elementData(index);//获取数组下标对应的值
    }

list.size()

public int size() {
        return size;//直接返回size属性
    }

总结:对list的操作其实就是对elementData数组的操作,需要注意有System.arraycopy调用的地方,说明当数据量大时,如果还频繁调用会很耗时。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值