「源码学习」集合之ArrayList

309 阅读6分钟

集合是最常用的数据结构之一。在日常开发中,ArrayList也算是我们使用频率较大的数据存储类了。

它底层基于数组实现容量大小动态变化。允许 null 的存在。同时还实现了 RandomAccess、Cloneable、Serializable 接口,所以ArrayList 是支持快速访问、复制、序列化的。

至于为么它不是线程安全的,看完源码便不言而喻了。

下面是基于JDK1.8 的 ArrayList。

主要属性

   //默认初始容量
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 有参构造函数的空数组赋值
     * 空实例的共享空数组实例
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 无参构造函数的空数组赋值
     * 与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存储元素的数组
     * 不序列化这个字段
     * ArrayList实现序列化反序列化方法writeObject、readObject
     */
    transient Object[] elementData;

    //存放元素的个数,不是elementData的长度
    private int size;

构造函数

 public ArrayList(int initialCapacity) {
        /**
         * 有参构造函数
         * initialCapacity > 0 : 初始一个大小为 initialCapacity 的 elementData 数组
         * initialCapacity = 0 : 把 EMPTY_ELEMENTDATA 赋值给 elementData
         */
        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() {
        /**
         * 无参构造函数
         * 把 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给 elementData
         */
        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()、remove()、set()、get()方法。

add(增)

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E element) {
        //检查index合法性
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //通过 System.arraycopy() 方法将旧数组元素拷贝至一个新的数组中去
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        elementData[index] = element;
        size++;
    }

    private void ensureCapacityInternal(int minCapacity) {
        /**
         * 这里就可以看出 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 和 EMPTY_ELEMENTDATA 的区别
         * 当第一次调用add,判断如果是无参构造函数创建的对象,此时 minCapacity=1
         * 则将 DEFAULT_CAPACITY 作为 初始容量
         */
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

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

        //如果 minCapacity 大于 elementData 的长度,则对集合进行扩容。
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    // 扩容操作
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 右移一位(除以2),等价于扩容至原来容量的 1.5 倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果 newCapacity < minCapacity ,取 minCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        /**
         * 如果1.5倍太大或者我们需要的容量太大,
         * 则 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE 进行扩容
         */
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 复制到新的数组
        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;
    }

MAX_ARRAY_SIZE 为么是 Integer.MAX_VALUE -8 ?

因为自己作为数组,除了存储数据本身以外,还需要32 bytes的大小来存储对象头(objectheader)信息。Java每个对象都包含了对象头,HotSpot虚拟机中对象头的大小不会超过32 bytes,所以最大容量减8才不会溢出。 Java对象在堆内存中的存储布局可以分为三部分:对象头(object header),实例数据(Instance Data)和对齐填充(Padding)。

remove(删)

public E remove(int index) {
        // 合法性检查
        rangeCheck(index);

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

        int numMoved = size - index - 1;
        // 判断删除的元素是否是最后一个位置
        if (numMoved > 0)
            //将从 index + 1 开始向后所有的元素都向前挪一个位置
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);
        // size - 1 ,将最后一个元素置空
        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++)
                // 遍历删除元素为null的位置
                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) {
        // 删除逻辑和remove基本一致
        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
    }

在add()和remove() 有一个操作数组数据的工具方法 System.arraycopy() ,它是如何实现的呢?

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

方法入参参数含义
Object src原数组
int srcPos从原数组的起始位置
Object dest目标数组
int destPos从目标数组的起始位置
int length复制的数组长度

arraycope()在ArrayList中主要是为了实现添加或删除元素的时候,右移(添加元素时)或左移(删除元素时)数组数据,下面简单模拟一下添加元素。

public class Test {
    public static void main(String[] args) {
        String[] arr =new  String[10];
        arr[0] = "0";
        arr[1] = "1";
        arr[2] = "2";
        arr[3] = "3";
        arr[4] = "5";
        int size = 5;
        /**
         * 在index = 1 的位置添加数据 ”x“
         * 源码中,添加时的操作: System.arraycopy(elementData, index, elementData, index + 1,size - index);
         */
        int index = 1;
        System.out.println(Arrays.toString(arr));
        System.arraycopy(arr, index, arr,index+1,size - index);
        arr[index] = "x";
        System.out.println(Arrays.toString(arr));
    }
}
日志输出 >>>>
[0, 1, 2, 3, 5, null, null, null, null, null]
[0, x, 1, 2, 3, 5, null, null, null, null]

set(改)

set() 和 get() 方法,都很简单,不多解释。

public E set(int index, E element) {
        //检查合法性
        rangeCheck(index);

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

get(查)

public E get(int index) {
        //检查合法性
        rangeCheck(index);

        return elementData(index);
    }

trimToSize

在日常使用ArrayList的时候,不常使用这个方法。但是这方法,也解答了我心中的疑惑,ArrayList扩容后,是不会自动缩小容积的,可以调用trimToSize()方法,实现缩小数容积。

public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

手撕代码实现ArrayList

/**
 * 仿照ArrayList,简单实现MyArrayList
 */
public class MyArrayList<E> {

    private E[] elementData;

    private int size = 0;

    private E[]  DEFAULT_ELEMENT_DATA =  (E[]) new Object[10];

    /**
     * 构造函数
     */
    public MyArrayList(){
        this.elementData = DEFAULT_ELEMENT_DATA;
    }

    public MyArrayList(int initialCapacity){
        elementData =  (E[]) new Object[initialCapacity];
    }

    /**
     * 增
     */
    public boolean add(E e){
        ensureCapacity(size + 1);
        elementData[size++] = e;
        return true;
    }
    public boolean add(int index, E e){
        if(index < 0 || index >= size){
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
        ensureCapacity(size + 1);
        for (int i = size ; i > index; i--) {
            elementData[i] = elementData[i-1];
        }
        elementData[index] = e;
        size++;
        return true;
    }
    /**
     * 删
     */
    public boolean remove(int index){
        if(index < 0 || index >= size){
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
        elementData[index] = null;
        for(int i=index; i<size; i++){
            elementData[i] = elementData[i+1];
        }
        elementData[size-1] = null;
        size--;
        return true;
    }
    /**
     * 改
     */
    public boolean set(int index, E e){
        if(index < 0 || index >= size){
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
        elementData[index] = e;
        return true;
    }
    /**
     * 查
     */
    public E get(int index){
        if(index < 0 || index >= size){
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
        return elementData[index];
    }

    /**
     * 判断数组容量 & 扩容2倍
     */
    private void ensureCapacity(int needCapacity){
        if(needCapacity > elementData.length){
            int newCapacity = elementData.length * 2;
            elementData = Arrays.copyOf(this.elementData,newCapacity);
        }
    }

    public Object[] toArray(){
        /**
         * 返回size长度数组
         */
        return  Arrays.copyOf(elementData, size);
    }

    /**
     * 数组越界异常信息
     * @param index
     * @return
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

    @Override
    public String toString() {
        return "MyArrayList{" +
                "elementData=" + Arrays.toString(toArray()) +
                ", size=" + size +
                ", length=" + elementData.length +
                '}';
    }
}
class MyArrayListTest{
    public static void main(String[] args) {
        MyArrayList<Integer> myArrayList = new MyArrayList<>();
        System.out.println("插入10个元素:");
        for (int i=0; i<10 ; i++){
            myArrayList.add(i);
        }
        System.out.println(myArrayList.toString());
        System.out.println("插入100:");
        myArrayList.add(100);
        System.out.println(myArrayList.toString());
        System.out.println("在index=7插入77:");
        myArrayList.add(7,77);
        System.out.println(myArrayList.toString());
        System.out.println("删除index=2数据:");
        myArrayList.remove(2);
        System.out.println(myArrayList.toString());
        System.out.println("修改index=8数据:");
        myArrayList.set(8,88);
        System.out.println(myArrayList.toString());
        System.out.println("获取index=9数据:" + myArrayList.get(9));
        System.out.println("获取index=10数据:" + myArrayList.get(11));
    }
}

日志输出

插入10个元素:
MyArrayList{elementData=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], size=10, length=10}
插入100:
MyArrayList{elementData=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100], size=11, length=20}
在index=7插入77:
MyArrayList{elementData=[0, 1, 2, 3, 4, 5, 6, 77, 7, 8, 9, 100], size=12, length=20}
删除index=2数据:
MyArrayList{elementData=[0, 1, 3, 4, 5, 6, 77, 7, 8, 9, 100], size=11, length=20}
修改index=8数据:
MyArrayList{elementData=[0, 1, 3, 4, 5, 6, 77, 7, 88, 9, 100], size=11, length=20}
获取index=9数据:9
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 11, Size: 11
	at com.yx.arraylist.MyArrayList.get(MyArrayList.java:77)
	at com.yx.arraylist.MyArrayListTest.main(MyArrayList.java:138)