JAVA集合框架源码分析1-ArrayList

java中的数据结构之集合框架源码分析的系列文章如下:
JAVA集合框架源码分析1-ArrayList
JAVA集合框架源码分析2-Stack
JAVA集合框架源码分析3-LinkedList
数据结构中有多种存储结构,如表和树,表中典型的是线性表,这种数据结构在java中最常见的就是ArrayList(顺序表)和LinkedList(链表),本文将着重对ArrayList的源码进行分析

1.继承关系

查看源码可知ArrayList是AbstractList的子类,而AbstractList是AbstractCollection的抽象子类,AbstractCollection实现了Collection接口,同时List是Collection接口的子接口,而且ArrayList实现了List接口,现在主要查看下List类中的源码,既然集合是存储数据的,那么必定存在增删改查四个操作,可以查看到List接口中有对应的如下四个方法

 boolean add(E e);
 boolean remove(Object o);
 E set(int index, E element);
 E get(int index);

2.ArrayList的成员变量


       private static final long serialVersionUID = 8683452581122892189L;

      // 默认的空间大小
        private static final int DEFAULT_CAPACITY = 10;

       //定义空数组,在有参数的构造方法(参数为容器大小),若参数为0,则给存储元素的数组赋值为空数组
        private static final Object[] EMPTY_ELEMENTDATA = {};

        //定义空数组,在无参数的构造函数中使用,给存储元素的数组赋值为空数组
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

        //定义数组用于存储容器中的元素
        transient Object[] elementData; // non-private to simplify nested class access

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

3.ArrayList的构造方法

   //构造方法的中的参数为容器的大小,逻辑关系很简单, 
  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 c) {
        //将参数转为数组并赋值给当前的数组容器
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray可能返回的不是 Object[] ,所以做一层类型的判断将其转为Object数组
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //若数组容器的长度为0,则将数组容器赋值为空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

4.操作——增加

第一种添加方法
注意以下方法的调用关系

 public boolean add(E e) {
        //确保数组容器有足够的存储空间,size为数组容器的元素个数
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

 private void ensureCapacityInternal(int minCapacity) {
        //说明是添加第一个元素,此时的容器大小为0,那么会在首次将容器的大小设定为默认大小,即10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        
        ensureExplicitCapacity(minCapacity);
    }

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

        // 确认是否需要扩容,如果元素个数+1即(size+1)>数组的长度,表示已经存满,需要进行扩容
        if (minCapacity - elementData.length > 0)
        //扩容
          grow(minCapacity);
    }

 private void grow(int minCapacity) {
        // 旧数组容器的大小
        int oldCapacity = elementData.length;

        //新数组容器的大小为旧的大小+旧的大小的一半,即ArrayList默认的扩容了当前数组大小的一半。
        int newCapacity = oldCapacity + (oldCapacity >> 1);

        //确保了newCapacity不会比minCapacity小
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;

        //确保了newCapacity的最大值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);

       //将数据拷贝到新数组容器中,并让elementData引用。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

第二种添加方法

public void add(int index, E element) {
        //确认添加元素的添加范围是否正确
        rangeCheckForAdd(index);
        //同上,确认是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //从需要插入元素位置开始的所有元素向后"移动"1位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //将在插入的位置赋值为插入的元素
        elementData[index] = element;
        //将数组长度增加1
        size++;
    }

 private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

第三种增加方法

 public boolean addAll(Collection 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;
    }

第四种添加方法

public boolean addAll(int index, Collection c) {
        //确认添加范围是否符合要求
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        //同上,确认是否需要扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            // 从需要插入元素位置开始的所有元素向后"移动"numNew 位
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        //将a数组拷贝到新数组。从index位置开始到index+numNew
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

5.操作——删除

第一种方法:根据角标删除

public E remove(int index) {
        //删除的角标的范围检查
        rangeCheck(index);

        modCount++;
        //获取到要删除的元素
        E oldValue = elementData(index);

         //删除元素时数组需要移动的元素个数
        int numMoved = size - index - 1;
        //从index+1位置开始的元素统一向左移动一位,导致index位置的元素会被后面的元素覆盖,从而起到删除的作用
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将原数组的最后一个元素设置为null
        elementData[--size] = null; // clear to let GC do its work

        //返回删除的元素
        return oldValue;
    }

 private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

第二种方法:直接删除元素

 public boolean remove(Object o) {
        //间接证明可以添加null元素
        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++)
                //使用equal对元素进行对比,找到元素
                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
    }

第三种删除方法:清空所有

 public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

第四章删除方法

protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        //将数组中从toIndex位置开始的元素移动(toIndex-fromIndex)位
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            //将newSize位置开始的元素设定为null,便于回收。
            elementData[i] = null;
        }
        size = newSize;
    }

5.操作——修改

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

6.操作——查找

//不做解释
public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

7.总结

1.ArrayList底层实现是数组。
2.当使用无参数构造函数创建ArrayList对象时,ArrayList对象中的数组初始长度为0,在第一次添加时才会设置数组的长度为默认长度即10
3.ArrayList扩容是将元素重新拷贝到新数组中,扩容的大小为旧数组的长度加上旧数组长度的一半。
4.ArrayList数据结构的特性导致查改操作比较快,而增删操作比较慢。

你可能感兴趣的:(JAVA集合框架源码分析1-ArrayList)