ArrayList的源码分析

六眼飞鱼酱① 提交于 2020-02-27 11:38:50

前言

我思故我在

ArrayList的简介

1,ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacityXXX 方法来操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

2,它继承了AbstractList,实现了 ListRandomAccessCloneablejava.io.Serializable 这些接口。

3,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)

4,继承AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

5,实现RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。对于实现了RandomAccess 接口的集合在使用Collections.binarySearch方法,的执行策略有所不同。

6,实现Cloneable 接口,即覆盖了函数 clone(),能被克隆

7,实现java.io.Serializable 接口,这意味着ArrayList支持序列化能通过序列化去传输

8,和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。

9,ArrayList的图解如下。

源码分析

首先我们先来看看ArrayList集合的相关属性和构造方法

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 设置默认容量为10
     */
    private static final int DEFAULT_CAPACITY = 10; 

    /**
     * 如果我们使用的是ArrayList(int initialCapacity)来创建ArrayList数组时,
     * 如果我们initialCapacity等于0那么直接用EMPTY_ELEMENTDATA,
     * 如果initialCapacity大于0那么this.elementData = new Object[initialCapacity];
     * 空数组(用于空实例)。
     */    
    private static final Object[] EMPTY_ELEMENTDATA = {};//来初始化我们的elementData

    /**
     * 如果我们使用的是ArrayList() 的无参构造方法,那么将使用
     * DEFAULTCAPACITY_EMPTY_ELEMENTDATA来初始化我们的elementData
     */  
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 用来存放集合中的元素 
     */
    transient Object[] elementData;// non-private to simplify nested class access

    /**
     * 集合中容纳元素的个数
     */
    private int size;

    /**
     * 有参构造方法,我们可以在初始化的时候自定义elementData数组的长度
     * @param 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);
        }
    }
    
    /**
    * 无参构造方法
    */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    /**
    * 有参构造方法,参数是集合
    */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
        
        //。。。。。。
}

Add方法:①在末尾添加②指定位置添加

    /**
    * 在末尾添加
    */
    public boolean add(E e) {
        //判断是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    /**
    * 在指定位置上添加元素
    */
    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++;
    }

ArrayList的扩容机制

我们要知道,ArrayList的扩容不是每次扩容只扩容一次,而是会扩容很多的空间。

通过add方法我们可以知道会调用了ensureCapacityInternal方法,这个就是在调用扩容机制。

ensureCapacityInternal(int minCapacity)的作用:得到最小扩容量

首先我们要知道ArrayList的扩容发生在添加元素阶段,比如我们要在x位置上添加元素,这个位置可能是集合尾部,集合中部或者集合前面,不管添加元素的位置在哪里,肯定是扩容数组的尾部。那么我们就要判断一下,需要扩容多少。

所以add方法调用ensureCapacityInternal(int minCapacity)方法,并传入参数size+1。

执行if语句,如果我们的的判断的结果是true,那么证明这是我们用无参构造方法ArrayList()生成的,所以得到最小的扩容量是DEFAULT_CAPACITY(默认值是10),则minCapacity=10。

如果返回的是false,证明elementData不是空数组,则minCapacity=size+1。

    /**
    * 得到最小扩容量
    */
    private void ensureCapacityInternal(int minCapacity) {//minCapacity,为最小扩容量
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);//传入最小扩容量
    }
    

然后我们可以看到调用ensureExplicitCapacity(minCapacity)方法。

ensureExplicitCapacity(minCapacity)的作用:明确扩容量

首先,我们看到这个方法中执行了modCount++操作,这个东西很重要,我们在后面进行讲解。

执行if语句,如果我们判断结果是true,就证明我们真的需要扩容了

如果返回false,证明不需要进行扩容,现在的数组长度,可以容纳这次添加元素

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        //检查数组的容量是否足够
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//真正扩容的方法
    }

执行真正的扩容方法grow(minCapacity)

    /*
    * 要分配的最大数组大小
    */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    private void grow(int minCapacity) {
        //旧容量
        int oldCapacity = elementData.length;
        
        /*
            新容量,在这个用的是位运算符,在这里呢,顺便提一提位运算符
            << : 左移运算符,num << 1,相当于num乘以2
            >> : 右移运算符,num >> 1,相当于num除以2
            >>> : 无符号右移,忽略符号位,空位都以0补齐
            A >>> B -- B 指定要移位值A 移动的位数。
            无符号右移的规则只记住一点:忽略了符号位扩展,0补最高位
             
             位运算的执行效率是比普通的乘除要高的
               
            所以说在这里呢,新容量更新为旧容量的1.5倍,
        */
        int newCapacity = oldCapacity + (oldCapacity >> 1);
    
        //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量
        //这个时候,有的人可能会想,不是已经得到了新容量,为什么还有与minCapacity最小容量进行比较呢?
        //这是因为,在ArrayList的方法中我们其实可以直接自己任命最小容量的值,
        //调用ensureCapacity()方法,这个方法的具体细节,现在不讲。
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;

        //再检查新容量是否超出了ArrayList所定义的最大容量,
        //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
        //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,
        //否则,新容量大小则为 MAX_ARRAY_SIZE。
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //执行Arrays.copyOf方法,数组的拷贝复制
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    //比较minCapacity和 MAX_ARRAY_SIZE
    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(E e) 方法的扩容

1,检查数组的容量是否足够。

2,够,直接添加元素,不够,扩容到1.5倍。

3,第一次扩容后,如果还是小于minCapacity,就将容量扩充为minCapacity。

4,然后添加元素。

接着我们看一看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));
   }

自定义扩容大小ensureCapacity方法

    //这个方法我们可以直接调用,可以自定义扩容大小
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                // any size if not default element table
                ? 0
                // larger than default for default empty table. It's already
                // supposed to be at default size.
                : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

get方法

    public E get(int index) {
        rangeCheck(index);//判断角标是否合法

        return elementData(index);//直接返回结果
    }
    
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

set方法

    public E set(int index, E element) {
        rangeCheck(index);//判断角标是否合法

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
    
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

remove方法:①按角标删除②按元素删除

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

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        
        //elementData:源数组
        //index + 1:源数组中的起始位置
        //elementData:目标数组
        //index:目标数组中的起始位置
        //numMoved:要复制的数组元素的数量;
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, elementData, index,
                    numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
    
    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中modCount的作用

在ArrayList中有个成员变量modCount,继承于AbstractList。

这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1。这到底有什么用呢?

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

public class Demo {
    public static void main(String args[]){

        ArrayList list = new ArrayList();

        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);

        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            Integer number = (Integer) iterator.next();
            if(number == 3){
                list.remove(number);
            }
            System.out.println(number);
        }
    }
}

我可以告诉大家这个代码会抛异常,ConcurrentModificationException

为什么,因为ArrayList被设计成非同步的,不能在遍历的时候进行修改和删除,这个就与modCount有关。

    //执行下面的代码
    Iterator iterator = list.iterator();
    
    
    public Iterator<E> iterator() {
        return new Itr();
    }
    
    //接着就会调用ArrayList内部实现的Iterator接口的类Itr
    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
        //会将自己的ArrayList里面的modCount复制给内部类中的expectedModCount
        int expectedModCount = modCount;  
        
        //调用遍历函数
        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];
        }
        
        //在这里面我们可以看到一个checkForComodification();
        //在这个方法中,if语句里面modCount != expectedModCount如果不等会抛异常。
        //我们可以知道如果我们在遍历的时候调用ArrayList的add或者remove方法,
        //会导致执行modCount++操作,从而导致异常
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        
    } 

ArrayList中RandomAccess 接口的作用

查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能

这个接口的作用要在调用Collections.binarySearch() 才有用。

在这里我们可以看到ArrayList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能

    public static <T>
    int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    }
    
    //实现了RandomAccess接口
    private static <T>
    int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
        int low = 0;
        int high = list.size()-1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = list.get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }
    
    //没有实现
    private static <T>
    int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
    {
        int low = 0;
        int high = list.size()-1;
        ListIterator<? extends Comparable<? super T>> i = list.listIterator();

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = get(i, mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }

list 的遍历方式选择:

实现RandomAccess 接口的list,其实可以看做是使用了for循环。

没有实现 RandomAccess 接口的list,其实调用的是iterator遍历。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!