ArrayList源码分析

好久都没有搞过源码了,再来复盘一下吧其实感觉这个源码没啥也就是看着玩吧

Arrays源码

api文档
文字介绍

首先,api文档里面的内容我们需要看一下,看一下官方是怎么说的具体的可以去很多地方去找api文档建议看英文的因为会有涉及到翻译的问题

可调整大小的数组的实现List接口。 实现所有可选列表操作,并允许所有元素,包括null 。 除了实现List 接口之外,该类还提供了一些方法来操纵内部使用的存储列表的数组的大小。 (这个类是大致相当于Vector,不同之处在于它是不同步的)。 
该size,isEmpty,get,set,iterator和listIterator操作在固定时间内运行。 add操作以摊余常数运行 ,即添加n个元素需要O(n)个时间。 所有其他操作都以线性时间运行(粗略地说)。 与LinkedList实施相比,常数因子较低。 

每个ArrayList实例都有一个容量 。 容量是用于存储列表中的元素的数组的大小。 它总是至少与列表大小一样大。 当元素添加到ArrayList时,其容量会自动增长。 没有规定增长政策的细节,除了添加元素具有不变的摊销时间成本。 

应用程序可以添加大量使用ensureCapacity操作元件的前增大ArrayList实例的容量。 这可能会减少增量重新分配的数量。 

请注意,此实现不同步。 如果多个线程同时访问884457282749实例,并且至少有一个线程在结构上修改列表,则必须在外部进行同步。 (结构修改是添加或删除一个或多个元素的任何操作,或明确调整后台数组的大小;仅设置元素的值不是结构修改。)这通常是通过在一些自然地封装了列表。 如果没有这样的对象存在,列表应该使用Collections.synchronizedList方法“包装”。 这最好在创建时完成,以防止意外的不同步访问列表: 

  List list = Collections.synchronizedList(new ArrayList(...)); The iterators returned by this class's个 iterator和listIterator方法是快速失败的 :如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身remove种或add方法,迭代器都将抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。 

请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力投入ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。 

这里里面涉及到了几个问题我们说一下

  • 首先ArrayLIst实现的List的接口也就是说他的底层数数组,也就是说他的查询的空间复杂度低,但是他的插入和删除的时间复杂度高
  • 其中可以包含null元素这个其实很重要的,因为很多时候在使用ArrayList的时候都需要考虑空的情况
  • 与Vector的差别,当在多线程的情况下的时候ArrayList可能会出现问题,使用Vector的话会比较的安全。在单线程的情况下使用Vector会比较浪费资源,因为Vector的方法添加了synchronized保证了线程的安全。所以单线程的情况下建议使用ArrayList,多线程的话建议使用Vector(后面的话有时间回去看看Vector的源码)
  • 然后就是ArrayList是快速失败的,具体来说就是ArrayList在遍历的时候使用,迭代器遍历的时候当你使用集合本身的删除方法删除集合内部的元素的时候,会提示一个错误,这个是我写这一篇的主要的理由,后面会具体的去看快速失败(fail-fast)的问题
构造方法
ArrayList() 
构造一个初始容量为十的空列表。  
ArrayList(Collection<? extends E> c) 
构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。  
ArrayList(int initialCapacity) 
构造具有指定初始容量的空列表。  
ArrayList()
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

插一句其中很有意思的一件事就是我去debug了一下源代码,可能很少有人这么做,因为ArrayList真的不是那种很难的集合,不致于debug去看但是我debug发现了一个有意思的东西
正常来说,去使用ArrayList的构造生成ArrayList会直接进入上述的构造方法我也不知道我咋测试出来,ArrayList在进入空构造之前需要去add方法,实话实说没有找到代理的位置但是我测试出来了一个位置的代码,当你在进入构造之前会进入到add方法中
在这里插入图片描述

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            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;
    }

个人理解,就像是在类的基础上处理ArrayList,首先去初始化他的大小,add方法我们在使用的时候在讲解一下,然后主要是ensureExplicitCapacity主要是使用了快速失败,这个在后面的文章里面会写,grow就是一个简单的扩容,首先计算出旧的大小就是新增元素之后的大小,然后是在旧的大小的基础上,扩大旧的大小的一半,右移符号就是扩大一倍的意思,if (newCapacity - minCapacity < 0) newCapacity = minCapacity;其实我没有看懂和这个的意思,感觉条件重复了,因为进入的grow方法的前提就是已经不满足使用了才回去扩容,if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);这个的意思就是如果这个大小超过了一定的大小的情况下就是直接把Integer的最大值赋给他。

ArrayList(int initialCapacity)
    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);
        }
    }

如果构造中有初始大小的话就要判断如果内容大于0的话就按照给定的大小初始化ArrayList,如果传入的参数是0则还是回去将一个空的数组进行初始化,其实就是走了无参的初始化,然后如果不是上述的两种可能性那一定穿的是负数,则可以直接抛出异常,

ArrayList(Collection<? extends E> c)
 public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }

当传进来一个Collection的接口的实现类的时候,Collection的实现类包括下图中
1.8的api
基本上都是List和Set组成的
Object[] a = c.toArray();取出基本的数组,然后将ArrayList的size变成数组的长度,然后判断数组的长度是否为0 ,如果为零则还是依旧那么给一个空的数组后面的话还是和空的构造那样初始化,如果不为零则判断传进来的集合的类型是否是ArrayList,如果是则直接相等,如果不是则利用Arrays的方法进行复制到指定的数组中完成对于ArrayList的初始化。

新增

先上代码

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            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;
    }

基本的add方法都说了,我说一点不一样的东西吧,这里牵扯到一个概念叫做快速失败,fail-fast,快速失败,就是当你在使用迭代器遍历的时候集合发生了变化就会报错,报错抛出异常ConcurrentModificationException

fail-fast

常见出错效果

import java.util.ArrayList;
import java.util.Iterator;

public class Test4 {
    public static void main(String[] args) {
        ArrayList list1 =  new ArrayList();
        list1.add("11");
        list1.add("33");
        list1.add("44");
        Iterator iterator = list1.iterator();

        for (; iterator.hasNext();) {
            String str = (String)iterator.next();
            list1.remove(0);
            System.out.println(str);
        }

//        while(iterator.hasNext()){
//           list1.remove(0);
//        }

    }
}

在这里插入图片描述
就是因为快速失败,当你在使用迭代器的时候对于元素进行了数据的处理,不管是增加数据还是减少数据都对于数据进行了修改,出错的原因用源码来讲解一下

private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            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() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

我们来梳理一下具体的情节,以我自己写的测试类为例Test4,首先新建ArrayList,add元素,调用ArrayList中的方法产生一个新的Itr类

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

产生一个新的Itr的类,进入Itr的空白构造函数,cursor = 0当前元素是第几个,lastRet = -1记录上次的元素是第几个,expectedModCount = 集合当前修改次数,hasnext()判断里面有没有元素,进入hasnext方法中,判断当前元素标号是否是元素的大小,只要不是元素内容的话就返回true。当确定编号对应的有数组中是有内容的就开始执行next()方法,开始取里面内容的方法,checkForComodification方法是在检查这个集合是否已经被改动了,ArrayList中所有改变元素的方法都会去增加这个修改次数的字段,从ArrayList这个集合建立之后就开始记录修改次数这个字段,当集合里面的元素发生任何变化的时候,迭代器iterator都会提示错误ConcurrentModificationException快速失败,那我们就不能修改里面的元素了吗?
可以,使用iterator自带的remove方法可以修改迭代器的内容,但是我真的没有想到任何可以使用的地方,感觉可以用其他方法避免这个问题。
官方文档说明

The iterators returned by this class's个 iterator和listIterator方法是快速失败的 :如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身remove种或add方法,迭代器都将抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。 

请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力投入ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。 

大概意思我理解应该,只能够保障单线程的快速失败生效,之所以使用迭代器的快速失败是为了保证安全,当你在使用迭代器迭代一个集合的时候可以最大程度上保证了你可以遍历到这个集合中的所有元素。
PS:迭代器所有的对元素的操作都是不会引发快速失败的,虽然我觉得这个操作没有啥意义。
其实这里面有一个安全失败的概念,但是我不在这里展开了,我想放到HashMap中或者后面会有ConcurrentHashMap类中去。

add(int index, E element)
public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
        private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

首先方法的话是可以在指定位置添加指定元素,然后首先就是需要去校验一下这个位置是否可以从ArrayList中取得到,就是下标越界,rangeCheckForAdd方法用来检查是否越界,这个ensureExplicitCapacity方法添加更改次数modCount,如果大小不够的话需要去进行扩容grow上面有这个方法的讲解,就不在此解释了,

addAll(Collection<? extends E> c)
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    

将一个Collection类型的集合放在参数里面,取其中所有的元素成为数组,计算集合中的元素个数进行扩容,使用System的方法复制数组,我们知道Arrays.copyOf的方法,也是可以用来复制元素的,但是很明显没有办法实现这个需求所以我们使用 System.arraycopy方法,这个方法的参数,

    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

src原数组也是你需要复制的数组,srcPos你需要复制的元素的开始坐标,dest需要复制的元素,就是你需要的数组,destPos开始复制的位置,length复制的长度,还需要注意destPos+length不要超过dest的元素个数。

addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

这种指定位置的插入都是需要去考虑插入的位置是否是合法的,具体的方法和上面的方法使用是一样的,就是System.arraycopy的参数不一样,因为是指定位置。

删除
remove(Object o)
    public boolean remove(Object o) {
        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;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }    

删除方法,这个方法是将一个元素从集合中删除,因为ArrayList中可以存储多个重复的元素所以删除操作将会是对于所有的元素的,将集合中所有的这个元素进行删除,这里在删除的做了一个排除控制的操作,如果是null 的话就需要使用 == 进行判断,因为null是没有.equals的,找到指定的元素使用fastRemove方法,修改次数加一,将指定位置的元素向前复制一份,最后一个位置置为null

remove(int index)
public E remove(int index) {
        rangeCheck(index);

        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;
    }

其实也没啥好说的就是判断是否越界,修改次数加一,取出对应的元素,计算位置,将元素位置复制,将集合最后一个位置置为null,返回被删除的元素。

removeAll(Collection<?> c)
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }
   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 {
            // 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;
    }
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    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;
    }

removeAll这个方法是你有一个集合的数据需要去处理,在一个集合中删除另一个集合中所有数据,我们将传入的参数集合成为删除集合,将我们本身的集合成为原集合,requireNonNull判断传入的参数是否为空,batchRemove批量删除的方法,取出原集合中的所有元素,开始遍历,c.contains(elementData[r]) == complement判断删除集合中有的元素=flase,也就是只有当删除集合没有的元素才在原集合中进行添加,如果是删除集合中有的话则会跳过,判断元素是通过各个集合自己重写的contains方法,我这里都是使用的ArrayList所以就直接使用了ArrayList重写的contains,就是判断indexOf的结果是否大于0,indexOf方法将传入元素进行区分,如果是null则使用==判断,如果不是null则使用equals,集合里面存在元素的话则返回元素第一次出现的位置,否则则返回-1,if (r != size) {这个就是当面上的contains方法出现问题之后,按照出问题的位置进行复制数组,但是我丝毫没有看出来哪里可能会出问题,但是我猜测出问题的话应该是在多线程的情况下出问题,当上面的contains出问题的时候,抓紧复制数组,将w补全,后面我们会有使用的地方,if (w != size) {这个位置就是当有所删除的时候进行一个增加修改次数,删除的位置后面全部置为null,缩减集合的大小。

修改
set(int index, E element)
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

其实就是很简单了,就是一个判读下标是否越界,然后取出原有位置的元素,然后在原有位置的元素放上新的内容,将旧元素返回。

查询
get(int index)
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

判断下标是否越界,取出响应的元素。

基本上上述的代码就是ArrayList的增删改查其他的也用但是频率其实挺低的,那就这样了这一篇ArrayList的源码解析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

又是重名了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值