ArrayList源码解析

ArrayList源码分析

ArrayList的成员变量

对于大多数人来说,大家都知道ArrayList的底层是用数组实现的,那么今天就来揭秘一下ArrayList的底层的实现原理。首先来看一下
ArrayList的成员变量

/**
     * Default initial capacity.
     */
    // 默认大小
    private static final int DEFAULT_CAPACITY = 10;

    // null数组,之后解释是干嘛的
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 默认是null数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // ArrayList真正意义上数组保存在这个数组中
    transient Object[] elementData; // non-private to simplify nested class access

    // 数组大小
    private int size;
    
    // List对象有一个成员变量modCount,它代表该List对象被修改的次数,每对List对象修改一次,modCount都会加1.
     protected transient int modCount = 0;

ArrayList的构造函数

ArrayList源码解析

可以看到以一共有三个构造函数。

// 指定大小,当参数合法的时候会初始化elementData数组,如果是0则是null数组
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);
        }
    }

// 不指定就是空数组
    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

   
// 如果参数是一个Collection的子类,则会调用Arrays.copyOf()方法    
    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;
     }


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

    //判断一个数组是否是空数组,如果是则返回默认大小(10)和传入参数的最大值,
    //如果不是,则返回传入的大小
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    //如果说传入的大小比现在数组长度要长,则会进行扩容操作。比如现在有10个元素,
    //则size的大小是10,elementData.length也是10,当我想再加入一个新的元素的时候
    //则发现这个时候长度不够了,则会进行扩容操作,即调用grow()方法这个时候minCapacity的大小是11
     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;
            //这个地方我们可以看到是oldCapacity右移了1个单位。比如原来是2,
            // 2的二进制是10,右边移动是01,索引新的大小是原来大小的1.5倍
            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);
        }
     //在底层调用的其实是 System.arraycopy(original, 0, copy, 0,
                                         Math.min(original.length, newLength));这个方法,这个方法其实是个native方法。
                                  
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
代码解释:
  Object src : 原数组
    int srcPos : 从元数据的起始位置开始
  Object dest : 目标数组
  int destPos : 目标数组的开始起始位置
  int length  : 要copy的数组的长度

扩展 ArrayList不是线程安全的

elementData[size++] = e;
// 这个操作不是原子操作

单线程执行这段代码完全没问题,可是到多线程环境下可能就有问题了。可能一个线程会覆盖另一个线程的值。

列表为空 size = 0。
线程 A 执行完 elementData[size] = e;之后挂起。A 把 "a" 放在了下标为 0 的位置。此时 size = 0。
线程 B 执行 elementData[size] = e; 因为此时 size = 0,所以 B 把 "b" 放在了下标为 0 的位置,于
是刚好把 A 的数据给覆盖掉了。
线程 B 将 size 的值增加为 1。
线程 A 将 size 的值增加为 2。

这样子,当线程 A 和线程 B 都执行完之后理想情况下应该是 "a" 在下标为 0 的位置,"b" 在标为 1 的位置。而
实际情况确是下标为 0 的位置为 "b",下标为 1 的位置啥也没有。




ArrayList 默认数组大小为 10。假设现在已经添加进去 9 个元素了,size = 9。

线程 A 执行完 add 函数中的ensureCapacityInternal(size + 1)挂起了。
线程 B 开始执行,校验数组容量发现不需要扩容。于是把 "b" 放在了下标为 9 的位置,且 size 自增 1。此时 size = 10。
线程 A 接着执行,尝试把 "a" 放在下标为 10 的位置,因为 size = 10。但因为数组还没有扩容,最大的下标才为 
9,所以会抛出数组越界异常 ArrayIndexOutOfBoundsException


add(int index, E element)

在指定的索引位置进行插入操作

public void add(int index, E element) {
        // 首先校验索引是否超过了真实数组的长度,这里指的是已经存放的元素的个数
        // 如果超过了则会报异常
        rangeCheckForAdd(index);
        // 这个上面已经提到过了
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 这个时候index位置之后的元素会向后移动一位,把index上这个位置空出来
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 在index元素上进行赋值操作
        elementData[index] = element;
        // 真实大小自增
        size++;
    }
    
    private void rangeCheckForAdd(int index) {
            if (index > size || index < 0)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

ArrayList源码解析

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

这里可以看出其实和add(int index, E element)这个方法一致,只不过是这个方法是空出来了collection长度个位置,然后调用了
System.arraycopy进行填充。

remove()

 public E remove(int index) {
        //检查索引值是否越界。如果越界则会抛出IndexOutOfBoundsException异常
        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;
    }

removeAll(Collection<?> c)

private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        // 循环遍历传入的collection,然后将collection中的元素首先放在
        // elementData数组中
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            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;
    }

这个方法的算法思想就是:首先找到当前数组中不包含的元素放在头部,然后将尾部需要删除的元素的长度上的元素都置为null。

外传一------System.arraycopy()的分析

System中提供了一个native静态方法arraycopy(),可以使用这个方法来实现数组之间的复制。对于一维数组来说,这种复制属性值传递,修改副本不会影响原来的值。对于二维或者一维数组中存放的是对象时,复制结果是一维的引用变量传递给副本的一维数组,修改副本时,会影响原来的数组。

外传二-------自己实现的List结构

package com.bobo.dataStructure.list;

/**
 * 线性表
 **/
public class MyList {
    private int[] array;

    private int size=0;

    private static final int DEFAULT_ARRAY_LENGTH =8;

    public MyList(int length) {
        array = new int[length];
    }
    public MyList() {
        array = new int[DEFAULT_ARRAY_LENGTH];
    }

    /**
     * 清空线性表
     */
    public void clearList()  {
        array = new int[0];
        size=0;
    }

    /**
     * 返回线性表的元素个数,这个不是线性表的长度
     * @return
     */
    public int listLength() {
        return size;
    }

    /**
     * 返回索引值为i的元素
     * @param i
     * @return
     */
    public int getElement(int i) {
        if (i < 0 || i > array.length) {
            throw new RuntimeException("数组越界异常");
        }
        return array[i];
    }

    /**
     * 判断线性表中是否存在这个元素,如果存在,则返回
     * 在数组中的位置,如果不存在则直接返回0
     * @param element
     * @return
     */
    public int locateElement(int element) {
        for (int i=0; i< array.length; i++) {
            if (array[i]==element) {
                return i+1;
            }
        }
        return 0;
    }

    /**
     * insert(i,e)将元素e插入到线性表i中的位置
     * 如果position超过现在数组的长度,则扩展到position*2个数组的大小
     * 然后再进行插入
     * 1,2,3,4
     */

    public void insert(int position, int newElement) {
        if (position <= 0) {
            throw new RuntimeException("索引值参数错误");
        }

        if (position >= array.length) {
            expandArray(position*2);
            array[position-1]=newElement;
        } else{
            for (int i= array.length;i>position;i--){
                array[i-1]= array[i-2];
            }
            array[position-1]=newElement;
        }
        size++;
    }

    /**
     * 对数组大小进行扩容
     * @param size
     */
    private void expandArray(int size) {
        int[] newArray = new  int[size];
        for (int i=0;i<array.length;i++) {
            newArray[i]= array[i];
        }
        array = newArray;
    }

    /**
     * 打印出整个数据结构
     */
    public void printList() {
        for (int i =0;i<array.length;i++) {
            System.out.print(array[i] +" ");
        }
        System.out.println("");
    }

    /**
     * 在existElement前插入一个新的元素
     * @param existElement
     * @param newElement
     */
    public void insertBefore(int existElement, int newElement) {
        //首先定位已经存在的元素的位置
        int index = this.locateElement(existElement);
        if (index == 0) {
            throw new RuntimeException("existElement:"+existElement+"不存在");
        }
        insert(index,newElement);
    }

    /**
     * remove(i)删错线性表中索引为i的元素,并返回值
     * @param index
     */
    public int remove(int index) {
        if (index <0) {
            throw new RuntimeException("索引值不能为负值");
        }
        if(index > array.length-1) {
            throw new IndexOutOfBoundsException();
        }
        index = index-1;
        int temp = array[index];
        for (int i=index;i<array.length-1;i++) {
            array[i]= array[i+1];
        }
        size--;
        return temp;
    }

    /**
     * remove(e)删错线性表中e元素,成功返回true;
     * @param element
     */

    public boolean removeElement(int element) {
        while (true) {
            int index = locateElement(element);
            if(index == 0) {
                break;
            }
            remove(index);
        }
        return true;
    }

    /**
     * replace(i,e)替换线性表中索引为i的数据元素为e,返回原数据元素,如果i越界则报错;
     * 应该是把校验数据的代码拿出来
     * @param index 索引值
     * @param element 新元素值
     */
    public int replace(int index, int element) {
        if (index <0) {
            throw new RuntimeException("索引值不能为负值");
        }
        if(index > array.length-1) {
            throw new IndexOutOfBoundsException();
        }
        int temp = array[index-1];
        array[index-1] = element;
        return temp;
    }

    public static void main(String[] args) {
        MyList myList = new MyList();
        myList.insert(1,1);
        myList.insert(2,1);
        myList.insert(3,3);
        myList.printList();
        myList.removeElement(1);
        myList.printList();

      /*  myList.insert(1,1);
        myList.printList();
        myList.insert(10,2);
        myList.printList();
        myList.insertBefore(2,3);
        myList.printList();*/
    }
}