ArrayList源码分析

注:以下源码基于jdk1.8

继承结构与层次关系

在这里插入图片描述

拆分一下
在这里插入图片描述在这里插入图片描述

构造函数

无参构造函数

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 赋值为默认空数组
    }

带参构造函数

    /**
     *  构造一个有初始容量的列表。
     *      如果 initialCapacity < 0 则抛出异常
     * @param initialCapacity
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) { // 构造一个 初始容量为 initialCapacity 的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) { // initialCapacity 为 0,赋值为空数组
            this.elementData = EMPTY_ELEMENTDATA; 
        } else { // initialCapacity 小于 0,跑出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    /**
     *  将一个 Collection 集合作为 ArrayList
     * @param c
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class) //如果 c.toArray 返回的不是一个 Object数组,则利用Arrays.copyOf转化
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else { // 若 Collection集合长度为 0,赋值为空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

注意: ArrayList 无参构造器初始化时,默认为空数组,并不是大家常说的容量为 10,10 是第一次 add 时扩容的结果。

常用方法

1. 增

add

新增就是往数组中添加元素,主要分成两步:

  • 判断是否需要扩容
  • 赋值

 add 有四个相关方法

    public boolean add(E e) 在列表末尾添加元素 e
    public void add(int index, E element) 在下标为 index 处添加元素 e
    public boolean addAll(Collection<? extends E> c) 在列表末尾顺序插入c集合中的元素
    public boolean addAll(int index, Collection<? extends E> c) 在指定位置开始顺序插入c集合中元素
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 确保容量充足
        elementData[size++] = e;
        return true;
    }
    
     public void add(int index, E element) {
        rangeCheckForAdd(index); // 检查越界
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // elementData:要复制的数组  index: 从 index 处开始
        // elementData: 目标数组      index + 1:复制到数组第几个开始     size - index:复制长度
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }   

重点是 ensureCapacityInternal 方法,保证数组容量充足

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

    /**
     *  返回添加元素后所需要的容量( > 10)
     * @param elementData
     * @param minCapacity
     * @return
     */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 如果元素数组为默认的空数组,返回默认容量(10)与 minCapacity 中的较大者
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0) // 如果 所需容量 比当前容量大
            grow(minCapacity); // 扩容
    }

    /**
     *  扩容
     * @param minCapacity 所需最小容量
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;// 当前容量
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量 = 当前容量 + 当前容量 / 2
        if (newCapacity - minCapacity < 0) // 如果不够...
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0) // 如果新容量比jvm所能非配的最大容量(int 最大值 - 8)还大,
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity); // 复制数组
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

需要注意的有三点:

  • 扩容的规则是:扩容为原来的 3/2
  • 新增时,并没有对值 进行严格的校验,因此 是可以存储 null 值的。
  • 源码在扩容的时候,有数组大小溢出意识,就是说扩容后数组的大小下界不能小于 0,上界不能大于 Integer 的最大值。

扩容后,赋值操作很简单,elementData[size++] = e;便完成了,时间复杂度为O(1)(因为有复杂度震荡问题,所以即便算上扩容操作,也可以认为该方法可以在常数时间内完成),所以是线程不安全的。

2. 删

remove

remove相关方法有四个

public E remove(int index) 移除下标为 index 的元素 并返回
public boolean remove(Object o) 移除值为 o 的元素,移除成功返回 true,否则返回false
public boolean removeAll(Collection<?> c) 移除 集合 c 中的元素
public boolean removeIf(Predicate<? super E> filter)

remove(int index) 大致思想就是 将 index+ 1 之后的元素向前移动一个单位。

    public E remove(int index) {
        rangeCheck(index);
        //检查index是否在合法范围内,不合法抛出IndexOutOfBoundsException异常
        modCount++;
        E oldValue = elementData(index);//记录要移除的值
        int numMoved = size - index - 1;// 需要移动元素个数
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//将后面部分向前移动一个单元,元素多比较耗时。
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }

remove(Object o) 大致思想就是找到第一个值为 目标值 的下标,然后删除,删除方式与remove(int index) 相似,只是省去了边界检查而已。需要注意的是ArrayList支持存储 null 值,所以移除 null 是要另外判断。

    public boolean remove(Object o) {
        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); // 删除,因为下标肯定合法,所以 fastRemove 中不进行下标检查
                    return true;
                }
        }
        return false;
    }

    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1; // 需要移动元素个数
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

由于删除的时候需要移动你数组,所以remove的时间复杂度为 O(n)

clear

void clear() 移除此列表中的所有元素:将所有元素置为null 再将size归零

    public void clear() {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

3. 查

get

get(index) 获取下标为 index 处的元素并返回。方法很简单,就是返回数组下标所对应的元素

    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }

    E elementData(int index) {
        return (E) elementData[index];
    }
indexOf

indexOf(Object o) 返回列表中第一个与 o 的值相同的元素的下标,如果没有返回 -1。与之对应的还有 lastIndexOf(Object o) ,不同的是返回最后一个与之对应的下标。方法也类似,只是从后往前遍历而已。

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

boolean contains(Object o) 判断在列表中是否存在元素 o,其思想是找到 o 的下标,如果大于 0则表示存在,否则表示不存在

clone

Object clone() 返回此 ArrayList 的浅拷贝副本,其中会调用父类的clone方法,关于java中clone见:http://blog.csdn.net/zhangjg_blog/article/details/18369201

    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

Object[] toArray() 返回一个包含此列表中所有元素的数组。 容量刚好为元素个数。

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

4. 改

set

E set(int index, E element) 用指定的元素替代此列表中指定位置上的元素。 直elementData[index] = element,注意检查index的合法性。

    public E set(int index, E element) {
        rangeCheck(index);
        E oldValue = elementData(index);// 保存旧值
        elementData[index] = element;//赋新值
        return oldValue;
    }

5. 迭代

 如果要自己实现迭代器,需要实现 java.util.Iterator。ArrayList也是这样做的。ArrayList的迭代器叫做Itr.

    public Iterator<E> iterator() {
        return new Itr();
    }

Itr 主要有下面几个参数

int cursor;       // index of next element to return 迭代过程中,下一个元素的位置,默认从 0 开始
int lastRet = -1; // index of last element returned; -1 if no such 表示上一次迭代过程中,索引的位置
int expectedModCount = modCount; // 表示迭代过程中,期望的版本 modCount 表示数组实际的版本号。

迭代器一般来说有三个方法:

  • hasNext 还有没有可以迭代的,返回值为boolean类型
  • next 如果有值可以迭代,返回迭代的值
  • remove 删除当前迭代的值
hasNext
    public boolean hasNext() {
        // 如果 下一个元素的位置 != size(元素数量),返回true,表示还有元素没有迭代;否则返回false
        return cursor != size;
    }
next

next 主要做了两件事:

  • 检验能不能继续迭代
  • 找到迭代的值,并为下一次迭代做准备
    @SuppressWarnings("unchecked")
    public E next() {
        //迭代过程中,判断版本号有无被修改,有被修改,抛 ConcurrentModificationException 异常
        checkForComodification();
        int i = cursor;
        // 检验是否合法
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1; // cursor 加一,为下一次迭代做准备
        return (E) elementData[lastRet = i]; // 更新 lastRet,返回当前迭代元素
    }
remove
    public void remove() {
        // 如果上一次操作室,数组的位置已经小于 0 了,说明数组应被删除完了
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet); // 调用 ArrayList的 remove 方法删除下标为 lastRet 的元素
            // 更新 cursor 和 lastRet
            cursor = lastRet;
            lastRet = -1; // 表示元素已经被删除,也放置重复删除
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

注意:

  • lastRet = -1 的目的是防止重复操作,如果调用了remove之后,紧接着有调用remove,会直接跑出异常(IllegalStateException)

线程安全

 ArrayList 的各种操作并没有加锁,而且ArrayList自身的变量 也不是 volatile(可见)的,所以不是线程安全的,如果有多个线程对这些变量进行改变时,可能会有值被覆盖的情况。所以要注意 当 ArrayList 作为共享变量是的线程安全问题。

Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be “wrapped” using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list:
List list = Collections.synchronizedList(new ArrayList(…));

类注释建议我们使用过 Collections#synchronizedList(new ArrayList())的方法来保证线程安全。SynchronizedList是Collectins中的一个内部类,通过在方法上面加锁来实现,因此性能大大降低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值