ArrayList源码分析

ArrayList源码分析

我们要了解ArrayList的源码,首先要了解它的继承和实现的接口。通过查看API我们可以了解到:

        public class ArrayList<E>
        extends  AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, Serializable

我们可以来看看图解,比较直观(不得不承认,IDEA是最好的用的java编译器,哈哈):ArrayList图解
从上图我们可以看出来ArrayList继承了AbstractList类,实现了三个接口:Serializable;Cloneable;RandomAccess;可以使ArrayList具有序列化和克隆的功能。

属性

接下来我们看一下ArrayList的属性都有哪些?

 transient Object[] elementData; // non-private to simplify nested class access

  private int size;

以上是他的两个属性,我们从中可以看出,ArrayList主要用elementData数组的形式来存储数据。见名知意。这一点也可以从属性名中看出来。而size就是ArrayList的长度了;
看完属性,其实里面还有一些常量值值得我们去观察。ArrayList常量
1.默认属性容量

private static final int DEFAULT_CAPACITY = 10;

2.共享空常量数组

 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//共享空常量数组(默认容量空数组)
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组

构造方法

1. ArrayList() 构造一个初始容量为 10 的空列表。

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

从上面的构造方法中我们可以知道ArrayList就是先构造一个长度为10的空列表。但是它为什么是10?
其实我们通过查看源码,其实就可以发现,初始化的时候其实并没有给长度,是在add()方法中调用了ensureCapacityInternal()方法;

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
   //里面调用了ensureCapacityInternal()方法,我们接着看ensureCapacityInternal()方法。
      private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

其实可以从上面的代码看出来,就是说现在新建一个ArrayList的时候。肯定要使用add方法,如果你添加的长度小于10的话,就用10。超过十,就用你需要添加的长度。
2. ArrayList(Collection c)
构造一个包含指定 collection 的元素的列表
将容器Collection转化为数组赋给数组elementData,还对Collection转化是否转化为了Object[]进行了检查判断。如果Collection为空,则就将空的常量数组对象EMPTY_ELEMENTDATA赋给了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;
        }
    }

3. ArrayList(int initialCapacity)
构造一个具有指定初始容量的空列表。
根据参数的大小作为容量来实例化底层的数组对象,其中对参数的3中情况进行了处理。当参数小于0时,抛异常。当参数等于0时,用空的常量数组对象EMPTY_ELEMENTDATA来初始化底层数组elementData。

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

常用方法

add()

上面我们已经看过add的方法了,现在深入了解一下,ArrayList到底是怎么实现存储增加。
1.add(E e)

//首先add()方法进来,调用了ensureCapacityInternal()方法,把长度+1赋了进去,数组长度++,把元素添加到最后一个位置。并且返回的一直是true。
   public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
/*接下来就是ensureCapacityInternal()方法,其实我们从这个方法中可以看出elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
相等时,说明当前数组为空,则数组长度,则看现在的长度是否大于10,大于则为当前长度,小于则默认为10。
从这里我们可以明确看出来,我们使用无参构造的时候,默认长度为10。
    */
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
/*从这个方法又进入ensureExplicitCapacity()方法*/
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    //我们再来看看grow()方法。
/*
    这个grow()方法是一个很核心的方法。我们可以看到,它先是把旧数组的长度保存到oldCapacity里面。
    然后又重新创建了一个newCapacity,它的长度是又旧数组的长度+旧数组长度向右移一位(相当于除以2)
    ---->这也就是我们常说的数组的扩容机制。
    下面就是两个if判断,第一个判断是怕扩容的数组长度还是太小,就用minCapacity 来进行对数组的扩张。
    第二个判断是如果扩张1.5倍太大或者是我们所需的空间大小minCapacity太大,则进行Integer.MAX_VALUE来进行扩张。
    (其中hugeCapacity还有防止堆栈溢出,具体可以参考详细方法)
    最后使用Arrays.copyOf()方法来实现对数组数据一个拷贝功能。
*/
    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) //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    //最后再来看看Arrays.copyOf()方法的具体实现。最后归根结底是用的System.arraycopy()方法(这个方法命名不规范!因为很早就有这个方法了,所以一直没有改。)
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    //copyOf()方法的具体实现;
     public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

2.add(int index, E element)
现在看这个方法就简单很多,就是插入到指定位置,其实就是对 System.arraycopy()方法的操作。System.arraycopy()改变的是第二个数组,就是把第一个数组从srcPos的位置开始复制length长度,到第二个数组的destPos位置。
像System.arraycopy(elementData, index, elementData, index + 1,size - index);
其实就是把第一个数组长度先扩容,然后在index位置,把后面的数组再往后移动一个位置。
例如:{1,2,3,4,5,} 变成{1,2,3,3,4,5}

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

get()

接下来我们继续看get()方法,顾名思义,get就是取值。我们具体看一下源码。

    public E get(int index) {
        rangeCheck(index);
        //有效性检查
        return elementData(index);
    }
        //我们在来看看elementData(index)方法。
     E elementData(int index) {
        return (E) elementData[index];
    }

其实我们从这里就可以看出来,get()方法其实就是调用底层的数组elementData[],然后取指定下标的值。

set()

其实这个方法也很简单,因为底层使用的是数组,所以直接从数组中取出当前下标的元素,然后把新元素赋给当前位置,最终返回旧元素。

 public E set(int index, E element) {
        rangeCheck(index);
        //有效性检查
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

remove()

1.remove(int index)
这个方法是删除指定位置的元素,首先取出index位置的元素,定义一个numMoved ,它的长度就是index元素之后的长度。然后还是调用arraycopy方法,把从index位置之后往前挪了一位,然后再把最后一位赋为null。让gc回收。

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

2.remove(Object o)
这个方法是删除一个指定的元素,从源码中可以看到,无论是指定对象o是否为null,都是在ArrayList中找到与此第一个相等的元素的位置,然后调用fastRemove(index)来进行移除;如果没有找打指定对象o的位置,则返回false,表示没有移除成功。

 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;
    }
    /*里面还有这个方法fastRemove(index);其实我们可以看出来这个和remove(int index)
很相似,只是最后并没有返回元素,因为传的参数就是当前的元素,没必要返回了*/
    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
    }

remove方法待续…

clear()

最后我们来介绍一下这个方法,先来看一下源码。

    public void clear() {
        modCount++;

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

        size = 0;
    }

其实这个方法也不难理解,就是循环底层数组,然后把所有的值都赋为null。

如有不正之处,希望大家可以指出,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值