ArrayList源码学习

来源:互联网 发布:mac系统如何下载软件 编辑:程序博客网 时间:2024/05/20 04:11

ArrayList类

ArrayList
继承自:AbstractList
实现接口: List, RandomAccess, Cloneable, java.io.Serializable

属性

//实现Serializable接口,生成的序列版本号
private static final long serialVersionUID = 8683452581122892189L;

//默认初始化容量
private static final int DEFAULT_CAPACITY = 10;

两个空数组对象
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
你一定很好奇为什么弄两个??除了名字其他完全一样啊,有必要吗?
先看官方解释:

We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.

用来区分当第一次添加元素时,需要扩容的大小。之前自己没注意到这个细节,后边在 add() 操作中判断是否扩容时会有分析。

//ArrayList中实际存储元素的数组,看到这里是不是感觉我写错了?我也搞不懂为什么 elementData 属性设置默认访问权限,莫非是因为有 transient 不序列化的原因?
transient Object[] elementData;

//最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//记录当前ArrayList的长度,而不是 elementData 数组的长度,elementData数组会有值为 null 的位置,而且可能会很多。下面也会说到
private int size;

构造方法

当使用无参构造方法时,给 elementData 数组设置的是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
使用有参构造器且传入的值是0时,为 elementData数组设为 EMPTY_ELEMENTDATA
这里也没有本质区别,都是空嘛,下边的扩容操作才是亮点!

//注意没有一个构造方法设置 size 值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() {//使用默认初始化容量 10        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

add(E e)

ArrayList 在每次新增元素时,都会首先进行判断是否需要动态扩容。

注意上边的两个构造方法,分两类
1. 当使用无参构造器时,把 elementData 数组设置为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA终于派上用场了!在这种情况下进行「首次」 add() 操作时,会一次性把 elementData 数组大小设为 10 ,到这里扩容完毕。之后的第 2~9 次 add() 操作不会扩容,因为在判断是否需要扩容即执行ensureExplicitCapacity(minCapacity)函数时,传入的值是3-10,都小于等于执行完第一次 add() 操作后的数组大小10。只有在第十次 add() 操作时才会扩容,此时会扩容为原数组长度的 1.5倍。再之后再进行 add() 操作,会把当前 ArrayList 中的元素数量加一(size+1)和 elementData数组大小比较,若前者大,则继续扩容为当前数组长度的 1.5倍……
2. 使用有参构造器时,当进行 add() 时,只是把 size+1elementData.length()进行比较,若前置大就扩容至 1.5倍。

最后把 elementData[size]设置为 传入的值 e,并执行 size++操作。

public boolean add(E e) {        //每次添加都会先进行判断        ensureCapacityInternal(size + 1);          elementData[size++] = e;        return true;}private void ensureCapacityInternal(int minCapacity) {//使用无参构造器且首次添加元素时,设置最小扩容大小为10    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {                            //默认初始化容量10        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);    }    //判断是否需要扩容    ensureExplicitCapacity(minCapacity);}private void ensureExplicitCapacity(int minCapacity) {    //操作数+1    modCount++;//(**继承自AbstractList)    //判断 最小扩容容量>数组大小 :    if (minCapacity - elementData.length > 0)        //扩容:        grow(minCapacity);}//扩大至原来的1.5倍或者当前 size+1 ,谁大要谁private void grow(int minCapacity) {        int oldCapacity = elementData.length;        int newCapacity = oldCapacity + (oldCapacity >> 1);        if (newCapacity - minCapacity < 0)            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)            newCapacity = hugeCapacity(minCapacity);        elementData = Arrays.copyOf(elementData, newCapacity);    }

使用无参构造器和传入值为0的有参构造器扩容「区别」
前者是在首次 add() 时,会一下子把 elementData 数组的长度扩大到10,之后在第10次操作时才会扩容。
而后者在执行 add()时,第一次时会扩容为 1,elementData[0]=e;
第二次仍然会扩容至2,elementData[1]=e;
第三次仍然会扩容至3,elementData[2]=e;
第四次仍然会扩容至4,elementData[3]=e;
第五次仍然会扩容至6,elementData[4]=e;
……
扩容次数在前面几次会明显增加

remove(int index)

public E remove(int index) {    //检查角标是否合法:不合法抛异常    rangeCheck(index);    //操作数+1:    modCount++;    //获取当前角标的value:    E oldValue = elementData(index);    //获取需要删除元素 到最后一个元素的长度,也就是删除元素后,后续元素移动的个数;    int numMoved = size - index - 1;    //如果移动元素个数大于0 ,也就是说删除的不是最后一个元素:    if (numMoved > 0)        // 将elementData数组index+1位置开始拷贝到elementData从index开始的空间        System.arraycopy(elementData, index+1, elementData, index, numMoved);    //size减1,并将最后一个元素置为null    elementData[--size] = null;    //返回被删除的元素:    return oldValue;}
原创粉丝点击