jdk源码分析之 ArrayList

ArrayList 关系类图如下:
在这里插入图片描述
从ArrayList的类注解可以得知一些重要的信息如:

  • 允许 put null 值,会自动扩容;
  • size、isEmpty、get、set、add 等方法时间复杂度都是 O (1);
  • 是非线程安全的,多线程情况下,推荐使用线程安全类:Collections.synchronizedCollection();
  • 增强 for 循环,或者使用迭代器迭代过程中,如果数组大小被改变,会快速失败,抛出异常。

重要的成员变量
ArrayList的成员变量有:

// 默认的数组大小
private static final int DEFAULT_CAPACITY = 10;
// 用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};

// 共享的空数组实例,用于默认大小的空实例。我们将此与EMPTY_ELEMENTDATA区别开来,以了解何时需要
// 添加第一个元素。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};



/**
  * 存储ArrayList元素的数组缓冲区。
  * ArrayList的容量是此数组缓冲区的长度。任何
  * 包含elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList
     添加第一个元素时,
  * 将扩展为DEFAULT_CAPACITY。
  */
transient Object[] elementData;

// ArrayList的大小(它包含的元素数)
private int size;

ArrayList初始化
ArrayList初始化有三种方式:无参初始化,传入指定大小的initialCapacity初始化、指定初始数据的初始化。

  • 无参初始化:
 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • 传入指定大小的initialCapacity初始化。
   public ArrayList(int initialCapacity) {
        // 校验传过来的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(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray可能(不正确)不返回Object [] (see 6260652)
            // 如果集合元素类型不是 Object 类型,我们会转成 Object
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

新增及扩容
新增所需步骤为:

  1. 判空是否需要对ArrayList进行扩容操
  2. 直接进行赋值。

源码如下:

    /**
     * 新增到数组的后一个
     */
    public boolean add(E e) {
        //确保数组大小是否足够,不够执行扩容,size 为当前数组的大小
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 直接进行赋值操作
        elementData[size++] = e;
        return true;
    }

继续查看ensureCapacityInternal()方法

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

// 计算出ArrayList我们期望的最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果初始化数组大小时,有给定初始值,以给定的大小为准,不走 if 逻辑
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

// 传入一个期望的最小容量
private void ensureExplicitCapacity(int minCapacity) {
        // 记录ArrayList的版本修改
        modCount++;

        // 如果我们期望的最小容量大于目前数组的长度,那么就扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

具体扩容过程(grow():

/**
     * 增加容量以确保其至少可以容纳
     * 最小容量参数指定的元素数。
     * 扩容的核心就是把现有数据拷贝到新的数组里面去
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // oldCapacity >> 1 是把 oldCapacity 除以 2 的意思 扩容后的大小是原来容量的 1.5 倍;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果扩容后的值 < 我们的期望值,扩容后的值就等于我们的期望值
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果扩容后的值 > jvm 所能分配的数组的最大值,那么就用 Integer 的最大值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 数组拷贝
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

上面的翻译已经足够明确:但是还是需要注意滴!

  • 扩容的时候,如果真的需要扩容就就是原来的1.5倍。
  • 新增没有做一个数据校验所以是允许数据null进行add的。
  • ArrayList 中的数组的最大值是 Integer.MAX_VALUE,超过这个值,JVM 就不会给数组分配内存空间了。

取值方法(get)
具体的方法实现如下:

 public E get(int index) {
        // 对传入的index做一个检验,如果是一个合法的参数直接从elementData取出索引为index的值,并将其方法
        rangeCheck(index);
        return elementData(index);
    }

 @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

删除方法(remove)

   public boolean remove(Object o) {
       // 如果要删除的值是 null,找到第一个值是 null 的删除,如果还有的话不影响哦,也就是不会删除
        if (o == 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) {
        // 记录数组的结构要发生变动了
        modCount++;
        int numMoved = size - index - 1;
        // numMoved 表示删除 index 位置的元素后,需要从 index 后移动多少个元素到前面去
        // 减 1 的原因,是因为 size 从 1 开始算起,index 从 0开始算起
        if (numMoved > 0)
             // 从 index +1 位置开始被拷贝,拷贝的起始位置是 index,长度是 numMoved
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; //数组最后一个位置赋值 null,帮助 GC
    }

ArrayList的迭代器
如果要自己实现迭代器,实现 java.util.Iterator 类就好了,ArrayList 也是这样做的,Iterator重要参数有如下几个:

int cursor;// 迭代过程中,下一个元素的位置,默认从 0 开始。
int lastRet = -1; // 新增场景:表示上一次迭代过程中,索引的位置;删除场景:为 -1。
int expectedModCount = modCount;// expectedModCount 表示迭代过程中,期望的版本号;modCount 表示数组实际的版本号。

ArrayList实现的Iterator
源码如下

 /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;     
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            //cursor 表示下一个元素的位置,size 表示实际大小,如果两者相等,说明已经没有元素可以迭代了,如果不等,说明还可以迭代
            return cursor != size;
        }

        @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;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            // 如果上一次操作时,数组的位置已经小于 0 了,说明数组已经被删除完了
            if (lastRet < 0)
                throw new IllegalStateException();
                //迭代过程中,判断版本号有无被修改,有被修改,抛 ConcurrentModificationException 异常
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                // -1 表示元素已经被删除,这里也防止重复删除
                lastRet = -1;
                // 删除元素时 modCount 的值已经发生变化,在此赋值给 expectedModCount
                // 这样下次迭代时,两者的值是一致的了
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

         // 版本号比较
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

ArrayList线程安全问题
我们需要强调的是,只有当 ArrayList 作为共享变量时,才会有线程安全问题,当 ArrayList 是方法内的局部变量时,是没有线程安全的问题的。

ArrayList 有线程安全问题的本质,是因为 ArrayList 自身的 elementData、size、modConut 在进行各种操作时,都没有加锁,而且这些变量的类型并非是可见(volatile)的,所以如果多个线程对这些变量进行操作时,可能会有值被覆盖的情况。

类注释中推荐我们使用 Collections#synchronizedList 来保证线程安全,SynchronizedList 是通过在每个方法上面加上锁来实现,虽然实现了线程安全,但是性能大大降低,具体实现源码:

public boolean add(E e) {
    synchronized (mutex) {// synchronized 是一种轻量锁,mutex 表示一个当前 SynchronizedList
        return c.add(e);
    }
}

ArrayList是使用transient修饰elementData如何做到序列化和反序列?
我们都知道被transient修饰的成员变量是不参与对象的序列化和反序列操作的。而ArrayList是自己提供了两个私有方法来自己内部自己实现序列化和反序列化操作分别为:writeObject()和readObject().如果采用外部序列化法实现数组的序列化,会序列化整个数组。ArrayList 为了避免这些没有存储数据的内存空间被序列化,内部提供了两个私有方法 writeObject 以及 readObject 来自我完成序列化与反序列化,从而在序列化与反序列化数组时节省了空间和时间。因此使用 transient 修饰数组,是防止对象数组被其他外部方法序列化。

性能问题(相对于LinkedList)
相比于LinkedList,ArrayList的新增和删除不一定比LinkedList高,具体得看数据量和场景。但是因为ArrayList实现了RandomAccess所以随机访问for性能远比LinkedList性能高。
使用技巧

  • 在已知存储多大数据的时候,直接使用带参的(指定ArrayList大小)的方式进行初始化,避免ArrayList进行扩容带来的数组多次搬移。
  • 批量操作的时候尽量不要使用for的方式然后一个add或remove,而是直接使用addAll()或removeAll()方法防止数组多少搬移或扩容的问题
  • ArrayList是线程不安全的,若是想使用其不存在线程安全的问题,则使用 Collections#synchronizedList 来保证线程安全。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值