ArrayList源码浅析

回顾下集合体系中,ArrayList的一些主要操作的原理,这里查看的Android中的java运行时的源代码,和标准JDK中的实现有一定差别,主要去掉了一些范围检测,提高了一定的使用性能。

成员变量

首先看下成员变量:

//如果不给定ArrayList的初始容量,那么默认为10
 private static final int DEFAULT_CAPACITY = 10;

 //如果初始化容量设置为0,那么使用这个作为初始化数组
 private static final Object[] EMPTY_ELEMENTDATA = {};

 //如果不设置初始化容量,那么使用这个作为初始化数组,和EMPTY_ELEMENTDATA 的区别是为了
 //在第一次添加元素的时候以便知道加入的元素的数量  
 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

 //存储元素的数组
 transient Object[] elementData; // non-private to simplify nested class access

 //ArrayList 包含的元素数量
 private int size;

构造方法

    //不传入任何参数的构造
    public ArrayList() {
        //执行默认的初始化
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    //传入初始化容量的构造
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
        //大于0,就使用传入的参数直接初始化数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        //如果传入了0,使用EMPTY_ELEMENTDATA进行初始化
            this.elementData = EMPTY_ELEMENTDATA;
        } else {//不能传入小于0的数,否则抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

//传入集合的构造
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//转换为数组赋值给elementData 
        if ((size = elementData.length) != 0) {//判断数组是否有内容
            // 判断Class对象是否为Object[].class
            if (elementData.getClass() != Object[].class)
            //不是的话,复制一个新的赋值给elementData 
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 如果传递的集合是空集合,那么使用EMPTY_ELEMENTDATA初始化
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

add方法

    //将指定的元素添加到队尾
    public boolean add(E e) {
        //进行可能的扩容操作
        ensureCapacityInternal(size + 1); 
        //直接将元素添加到数组末尾
        elementData[size++] = e;
        return true;
    }

//扩容操作
 private void ensureCapacityInternal(int minCapacity) {
         //如果没有使用初始化容量的构造方法,默认就是这个DEFAULTCAPACITY_EMPTY_ELEMENTDATA
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //判断DEFAULT_CAPACITY(默认10个)和传入的容量,哪一个大使用哪一个扩容
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //进一步确认
        ensureExplicitCapacity(minCapacity);
    }
//确认容量并扩容    
private void ensureExplicitCapacity(int minCapacity) {
        //添加或者移除会引起这个数字自增,理解为List的修改次数
        modCount++;

        // 判断传入的容量大于elementData的长度才进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

//核心扩容方法
private void grow(int minCapacity) {
        // 取出原始elementData的长度
        int oldCapacity = elementData.length;
        //进行1.5倍扩容
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果新容量比当前需求容量小,直接将当前容量赋值给newCapacity 
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
         //如果newCapacity超过MAX_ARRAY_SIZE,那么将扩容到Integer.MAX_VALUE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 使用指定容量扩容数组,返回一个新的扩容数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //返回容量上限,最大Integer.MAX_VALUE
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

总之add方法在elementData容量不够的情况下会进行扩容,如果初始化的10个容量不够的话,进行1.5倍扩容,如果还不够直接使用size+1,然后进行了容量上限的检查,最大支持Integer.MAX_VALUE个元素存储,最后使用Arrays.copyOf()方法进行数组复制。
看下两个参数的add方法:

//在指定位置插入指定的元素
public void add(int index, E element) {
        //索引大于size或者索引小于0,直接抛出异常
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        //扩容操作
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //原数组和目标数组都是elementData,因此是在原数组上进行扩容,将index后面的
        //元素挪动一位,这样index位置就可以插入新添加的元素了
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //在index位置上插入元素                 
        elementData[index] = element;
        size++;//size增加
    }

看看addAll()方法:

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();//转换为数组
        int numNew = a.length;//获取长度
        ensureCapacityInternal(size + numNew);  // 扩容
        //把需要插入的数组复制进elementData尾部
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;//size增长
        return numNew != 0;//返回插入结果
    }

在指定位置插入集合的addAll()方法:

    public boolean addAll(int index, Collection<? extends E> c) {
        if (index > size || index < 0)//不合理就抛出异常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        //同样是获取插入的长度,预先进行扩容操作
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //需要将index后面的元素全部向后移动,计算下index后面的元素数量
        int numMoved = size - index;
        if (numMoved > 0)
        //从index移动到index + numNew,预留出需要插入的集合数量
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        //移动完成后插入新的元素数组
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;//size增长
        return numNew != 0;//返回
    }

get方法

public E get(int index) {
    if (index >= size)//判断索引是否合法
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    //合法的话返回elementData索引下标的对象
    return (E) elementData[index];
}

set方法

在指定位置设置制定元素:

    public E set(int index, E element) {
        if (index >= size)//检测索引合法性
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        //找到指定位置的元素 
        E oldValue = (E) elementData[index];
        //更新指定的元素
        elementData[index] = element;
        return oldValue;//返回旧元素
    }

remove方法

首先看看移除指定位置的方法:

    //移除指定下标的元素,并返回移除的元素
    public E remove(int index) {
        if (index >= size)//检查合法性,不合法抛出异常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        modCount++;//修改标识自增
        E oldValue = (E) elementData[index];//取出数组的index位置的元素
        //确定需要进行移动的index后面的元素数量
        int numMoved = size - index - 1;
        if (numMoved > 0)
        //将index后面的元素向前移动一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //size自减,并将最后一位赋值为null,GC会负责回收
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;//返回移除的元素
    }

移除指定元素的方法:

public boolean remove(Object o) {
        if (o == null) {
        //是null的话循环找出第一个是null的对象,移除后直接返回
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                //移除操作
                    fastRemove(index);
                    return true;
                }
        } else {
        //不是null循环数组,找出数组中第一个相同的对象,然后移除返回
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
//移除方法
private void fastRemove(int index) {
        modCount++;//修改次数自增
        int numMoved = size - index - 1;//确定index后面需要移动的元素数量
        if (numMoved > 0)
        //同样将元素向前移动一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
                             //size自减,将最后的元素置Null,等待垃圾回收
        elementData[--size] = null; // clear to let GC do its work
    }

移除指定集合的方法:

    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);//如果传入的集合是Null,直接抛出空指针异常
        return batchRemove(c, false);
    }
    //批量移除方法
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                //循环数组,把不包含的对象随着w自增,全部移动到前面
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {//上面r循环一遍后是等于size的所以不走这里
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {//如果w==size说明没有包含的对象,只有不包含才走这里
                // clear to let GC do its work
                //这样从w索引开始后面的所有的都是多余的了,置Null,等待垃圾回收
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;//包含几个就自增几个标识
                size = w;//w赋值给size
                modified = true;说明修改成功置为true
            }
        }
        return modified;//返回
    }

相反的一个方法,保留传入的集合中的所包含的对象:

public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);//判断是否为null
        return batchRemove(c, true);//这里不同是传入了true
    }

    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
            //这里也是循环一遍的话,所有包含的元素已经挪到数组前面了
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            if (r != size) {//这里不会走到
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {//w其实就是包含的元素个数
                // clear to let GC do its work
                //依旧是把w索引的后面的值置为null,等待垃圾回收
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;//同样包含几个元素就自增几个标识
                size = w;//w赋值给size
                modified = true;说明修改成功置为true
            }
        }
        return modified;
    }

size方法

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

isEmpty方法

    public boolean isEmpty() {
        return size == 0;//判断size是否为0
    }

indexOf方法

查找指定对象的首次出现的索引位置:

    public int indexOf(Object o) {
        if (o == null) {
        //传入对象为Null的话,就找出第一个元素为null的索引返回
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
        //传入对象不为Null的话,就找出第一个和传入对象相同的元素的索引返回
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;//找不到返回-1
    }

lastIndexOf方法

查找指定对象的最后一次出现的索引位置:

    public int lastIndexOf(Object o) {
        if (o == null) {
        //传入对象为Null的话,从尾部开始查找,就找出第一个元素为null的索引返回
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
        //传入对象不为Null的话,从尾部开始查找,就找出第一个和传入对象相同的元素的索引返回
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

contain方法

    public boolean contains(Object o) {
        return indexOf(o) >= 0;//实际调用了indexOf方法通过索引来判断的
    }

clear方法

    public void clear() {
        modCount++;//修改标识自增

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;//所有的元素置Null,使用GC回收

        size = 0;//size恢复为0
    }

其他

还有一些其他的方法这里不再分析。

  • 本质上ArrayList使用数组进行元素的存储;
  • 实现了RandomAccess标记接口,可以通过下标进行快速的随机访问;
  • 实现了Cloneable接口,表明能被克隆;
  • 实现了Serializable接口,支持序列化;
  • 添加移除元素等操作可能会引发数组长度的变化;
  • 使用Iterator循环时候,ArrayList不能进行add,remove,clear或者其他修改其容量操作,会引发ConcurrentModificationException异常;
  • ArrayList不是线程安全的,Vector是线程安全的,可以使用Collections.synchronizedList()方法来获取一个线程安全的List

参考文章
Java集合源码分析(一)ArrayList

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值