Java源码解读之ArrayList源码解读

本文详细探讨了Java中ArrayList的源码,介绍了其作为动态数组的数据结构,非线程安全的特点,以及如何通过Collections.synchronizedList获得线程安全的ArrayList。文章还重点讲解了ArrayList实现的Serializable、Cloneable和List接口,成员变量,构造方法和其他关键操作。最后,总结了ArrayList的优缺点,包括快速随机访问和插入删除的效率,并提及了扩容算法及其特殊情况。

Java源码解读之ArrayList源码解读

简介

底层是可变长的数组,随着不断添加元素,其容量也会自动增长,ArrayList实现了List的所有可选操作,允许存放null元素。
但是,ArrayList不是线程安全的,如果想获取线程安全的List,可以通过
List list = Collections.synchronizedList(new ArrayList(...));来获取线程同步的List
我们查看Collections的源码,发现大部分的方法都加上了synchronized同步,只有四个方法没有同步,需要用户自己添加。
在这里插入图片描述

比如说调用iterator方法。

List list = Collections.synchronizedList(new ArrayList());
      ...
  synchronized(list) {
      Iterator i = list.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }

实现的接口

在这里插入图片描述

我们重点关注三个接口RandomAccess``Serializable``Cloneable。这三个接口都很特别,接口内部没有定义任何方法,只是起到一个标记作用。

  • RandomAccess

    标记其支持快速(通常是固定时间)随机访问。实现此接口的实例使用for循环遍历的速度要快于使用迭代器。

  • Serializable

    序列化接口没有方法或字段,仅用于标识可序列化的语义。类通过实现 Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。

  • Cloneable

    标记Object.clone() 方法可以合法地对该类实例进按字段复制。
    如果在没有实现Cloneable 接口的实例上调用Objectclone方法,则会导致抛出 CloneNotSupportedException异常。实现此接口的类应该使用公共方法重写 Object.clone

成员变量

//默认初始化的容量为10
private static final int DEFAULT_CAPACITY = 10;
//用于共享的空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//用于初始化时没有指定初始化大小的空数组。这个也是共享的空数组实例。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**存储ArrayList元素的数组缓冲区。ArrayList的容量是此数组缓冲区的长度.当数组是空的时候且elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,添加第一个元素,elementData数组会扩容到默认的容量10.
**/
transient Object[] elementData;
//ArrayList中包含数组的个数。
private int size;

构造方法

  • public ArrayList(int initialCapacity)
    
     /* 构造具有指定初始容量的空列表
     * 1.如果指定的初始化容量>0,那么就申请的数组空间。
     * 2.如果指定的初始化容量==0,那么elementData=EMPTY_ELEMENTDATA,也就是指向共享的空数组实例。
     * 3.如果指定的初始化容量<0,直接抛出异常
     */
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(int initialCapacity)

     /* 构造一个初始容量为10的空列表。
     * 其实这里还是一个空的数组,因为指向了空数组,
     * 当第一次添加元素时,才主动扩容到10,具体可以看add方法的逻辑.
     */
     
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • public ArrayList(Collection<? extends E> c)
    public ArrayList(Collection<? extends E> c) {
        //1.将指定集合转换成数组
        elementData = c.toArray();
        //2.设置size为转换成数组的长度,如果size不为0,其实就是size>0
        //
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            //这里是一个bug,有些情况下返回的不是object数组
            //如果转换的数组不是object数组,那么就创建一个新的object数组,并将元素拷贝进去
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            //如果长度是0,那么就直接是EMPTY_ELEMENTDATA空组数。
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

其他方法

  • public int size()
     /*** 
     ****元素的个数
     */
    public int size() {
        return size;
    }
  • public boolean isEmpty()
    /**
     * list是否为空,其实就是判断size是否为0
     */
    public boolean isEmpty() {
        return size == 0;
    }

  • public boolean contains(Object o)
    /**
     * 判断是否包含某个元素,只要有一个匹配的元素就可以
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

  • public int indexOf(Object o)

    /**
     * 返回指定元素在list中的位置,也就是下标或是索引
     */
    public int indexOf(Object o) {
        //1.如果指定元素是null,那么就遍历比较,返回第一个为null的索引
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            //2.如果元素不为null,那么遍历比较,使用equals,返回第一个匹配的索引
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        //3.如果没有匹配上,那么返回-1
        return -1;
    }

  • public int lastIndexOf(Object o)

     /** 查找最后出现的指定元素的位置。
     * 这个就不用多说了,和indexOf对比,indexOf是正序遍历,lastIndexOf是倒序遍历。
     *
     */
    public int lastIndexOf(Object o) {

        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

  • public Object clone()

    /**
     * 就如同我们开始所说的,实现了Serializable接口,那么就需要重写clone。
     */
    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);
        }
    }

  • public E get(int index)
    /**
     * 获取指定索引的元素
     */
    public E get(int index) {
        //1.检查是否越界
        rangeCheck(index);
        //2.返回对应的索引元素,因为是数组,直接通过下标进行定位。
        return elementData(index);
    }
  • public E set(int index, E element)
    /**
        替换指定位置的元素
     */
    public E set(int index, E element) {
        //1.检查是否越界
        rangeCheck(index);
        //2.获取旧的元素
        E oldValue = elementData(index);
        //3.设置指定索引的元素为指定新的元素
        elementData[index] = element;
        //4.返回旧的元素
        return oldValue;
    }

  • public boolean add(E e)
public boolean add(E e) {
        //1.检查是否需要扩充及扩容操作,将新的元素个数作为参数传入
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //2.元素个数size自增1,然后设置自增下标指向新的元素,也就是将新的元素放置到末位。
        //3.szie是在这里自增1,用于计数,记录数组中的元素个数。
        //并且设置新元素
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
        //计算容量
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //1.如果是空数组,也就是初始化Arraylist的时候是无参构造,那么第一次添加元素就需要
        //初始化容量到默认的容量10,返回10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //2.如果到这里,返回的就是新的元素个数,也就是size+1元素个数
        return minCapacity;
    }
    
        private void ensureExplicitCapacity(int minCapacity) {
        //1.用来记录list结构的变动次数
        modCount++;

        // overflow-conscious code
        //2.如果新的元素个数比现在的数组长度要大,那么就需要扩容了。
        //2.1如果是默认初始化,这个时候minCapacity为10,elementData为空数组,长度为0,那么
        //满足条件需要扩容。

        //2.2 如果第一次扩容到10,之后当添加到第11个元素时,minminCapacity为11,elementData为10,那么还需要扩容
        //依次类推。

        //2.3 如果初始化的时候指定了初始化的容量,比如说是5,那么在添加前五个元素之前,都不会触发扩容,当添加第六个元素时,
        //minCapacity是6,elementData.length为5,满足minCapacity - elementData.length > 0的条件,就需要扩容了。
        if (minCapacity - elementData.length > 0)
            //2.3 开始扩容,传入的参数,是新的元素个数(也是最小的容量要求),除了默认初始化第一次为10,其他都是size+1
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        //1.获取旧的容量,也就是数组长度
        int oldCapacity = elementData.length;
        //2.设置新的容量为旧的容量的1.5倍,
        //旧的容量向右移1位(对于计算机来说移位操作比较快),就是原来的1/2,加上oldCapacity,也就是原来的1.5倍。
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //3.如果新的容量还是比最小要求容量小,那么直接设置minCapacity为新容量。
        //这个有个疑惑,什么情况下newCapacity会比minCapacity小?
        //因为int有最大的表示类型Integer.MAX_VALUE,如果oldCapacity接近Integer.MAX_VALUE,那么新的newCapacity=oldCapacity + (oldCapacity >> 1)
        // 计算之后,newCapacity可能会超过Integer.MAX_VALUE,从而导致溢出,那么此时newCapacity就会小于oldCapacity。
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //4.如果newCapacity比MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)还大,也就是newCapacity接近于Integer.MAX_VALUE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //5.拷贝元素
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        //溢出判断,如果minCapacity为Integer.MAX_VALUE+1就会导致溢出
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //这里就有个问题,什么样的情况下才能使minCapacity=MAX_VALUE,
        //因为这个方法最外面一层还有一个判断newCapacity - MAX_ARRAY_SIZE > 0,也就是说
        //需要满足newCapacity>MAX_ARRAY_SIZE而且minCapacity > MAX_ARRAY_SIZE,这个时候minCapacity才等于Integer.MAX_VALUE。
        //是否永远也达不到这个条件???
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }


  • public void add(int index, E element)
    /**
     *
     * 在指定位置添加一个新元素。那么其后位置的所有元素要往后移。
     */
    public void add(int index, E element) {
        //1.检查是否越界
        rangeCheckForAdd(index);
        //2.检查是否需要扩容及扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //3.将elementData数组中的index位置后面的(size-index)个元素,拷贝到(index+1)的位置。
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //4.写入新的元素
        elementData[index] = element;
        //5.元素个数+1
        size++;
    }
  • public boolean addAll(int index, Collection<? extends E> c)

    public boolean addAll(Collection<? extends E> c) {
        //1.指定的集合转换成数组
        Object[] a = c.toArray();
        //2.要添加的元素个数
        int numNew = a.length;
        //3.检查是否扩容,如果是,进行扩容操作
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //4.将目标数组中的元素拷贝到elementData的末位
        System.arraycopy(a, 0, elementData, size, numNew);
        //5.元素个数增加numNew
        size += numNew;
        //5.如果指定集合不为空,返回成功。
        return numNew != 0;
    }

  • public E remove(int index)

    /**
     * 删除指定位置的元素
     */
    public E remove(int index) {
        //1.越界判断
        rangeCheck(index);
        //2.数组结构变化计数+1
        modCount++;
        //3.取出要删除的元素
        E oldValue = elementData(index);
        //4.要移动的个数
        int numMoved = size - index - 1;
        //5.
        //如果删除的是数组末尾的元素,那么其实不用移动数组,直接把末位的元素置成null就可以了。
        //如果不是末位的元素,那么就需要把目标位置后面的元素集体往前移动一位
        //比如说 一个数组 存了 1,2,3,4 四个元素,现在要删除2,那么就把3,4集体往前启动一位,
        //移动完成之后变成1,3,4,4,然后再把最后的元素变成null,最终也就是1,3,4.
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //6.最后的元素设置为null,元素size个数-1
        elementData[--size] = null; // clear to let GC do its work
        //7.返回旧的元素,也就是删除的元素
        return oldValue;
    }
  • public boolean remove(Object o)
 /**
     * 删除指定的元素,匹配到的第一个元素
     */
    public boolean remove(Object o) {
        //1.如果指定的元素是null
        if (o == null) {
            //2.遍历比较
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    //3.极速删除
                    fastRemove(index);
                    return true;
                }
        } else {
            //2.如果指定的元素不为null
            for (int index = 0; index < size; index++)
                //2.遍历比较
                if (o.equals(elementData[index])) {
                    //3.极速删除
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
  • public void clear()
    /**
    *
     * 删除所有的元素,也就是清空list
     *
     */
    public void clear() {
        modCount++;
        //遍历list,每个元素都设置为null
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        //元素个数为0.
        size = 0;
    }
  • public boolean removeAll(Collection<?> c)
    public boolean removeAll(Collection<?> c) {
        //1.判断目标集合是否是null,是的话抛异常
        Objects.requireNonNull(c);
        //2.开始批量删除
        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++)

                //elementData为[2,4,6]
                //c集合是[1,4]
                //complement == false是我们来分析
                //第一次循环之后,elementData为变为[2,4,6] w=1,r=1
                //第二次循环之后,elementData为变为[2,4,6],w=1,r=2
                //第三次循环之后,elementData为变为[2,6,6],w=2,r=3

                //那么执行,w!=size,就直接将w之后的元素置空,也就是变成[2,6]


                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

总结

ArrayList底层是可变长的数组,默认初始化容量为10,可以自动扩充容量。每次扩充的容量为旧的容量的1.5倍。正是因为数组的特性,如果随机访问数组中的元素,那么访问速度比其他数据结构会快。但是,删除,插入元素会调整元素的位置甚至是重新申请空间,拷贝元素,因此相对链表而言比较慢。

关于扩容算法,扩容之后newCapacity什么情况下会比oldCapacity小,请查看这篇文章。
ArrayList扩容的一点疑惑–什么情况下newCapacity - minCapacity < 0

欢迎大家评论交流,欢迎技术大佬,小白进群讨论:163690707(QQ群)
个人博客:请点击此处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值