什么是ArrayList?


   ArrayList是存储一组数据的集合,底层也是基于数组的方式实现,实际上也是对数组元素的增删改查;它的主要特点是

  • 有序;(基于数组实现)
  • 随机访问速度快;(进行随机访问的时候,只需要遍历所有的数组)
  • 可以为null;(基于数组实现)
  • 元素可以重复;(基于数组实现)
  • 线程是不安全的;(对于多个线程访问时,没有进行同步操作,之所以不安全也是为它的性能而设计,如果需要创建一个安全的ArrayList集合可以通过 Collections.synchronizedList(list);实现同步)

  

 ArrayList结构分析


 ArrayList类结构如下图:

    

 我们可以看到ArrayList实现了List、RandomAccess、Cloneable、java.io.Serializable四个接口和继承了一个AbstractList抽象类,下面我们就来进一步的分析它们各自的作用;

  • Iterable :实现此接口以便支持foreach语法。
  • Collection:包含了集合基本方法。
  • AbstractCollection:实现了Collection里面大部分方法。
  • List:继承自Collection接口,包含了对集合操作的一些方法。
  • AbstractList:继承自AbstractCollection实现List部分方法。
  • Serializable:序列化的接口。
  • RandomAccess 用来表明其支持快速随机访问。
  • Cloneable:标识其支持对象复制。

 

  ArrayList源码分析


下面我们主要从ArrayList属性构造方法核心的方法来进行解析:

 

ArrayList定义了两个私有属性:

    /**
     * 用于存储数据的数组
     */
    private transient Object[] elementData;

    /**
     * 数组元素的实际个数
     */
    private int size;

第一个属性主要是存储元素的一个数组, 第二个属性是用于记录数组元素的实际个数,而不是数组的长度。

 

ArrayList定义了三个构造方法:

    /**
     * 数组初始容量构造方法
     *
     * @param  initialCapacity  
     * @throws IllegalArgumentException 
     *    
     */
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)    //如果指定的初始容量为负数,将抛出参数异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];    //指定一个存储数组初始容量值为:initialCapacity
    }

    /**
     * 默认构造方法
     */
    public ArrayList() {
        this(10);    //默认给定初始容量为10
    }

    /**
     * 给定一个参数集合的构造方法
     * @param c 
     * @throws NullPointerException 
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();    //转为数组
        size = elementData.length;    //给定size初始值

        if (elementData.getClass() != Object[].class)    // 是否成功转化为Object类型数组
            elementData = Arrays.copyOf(elementData, size, Object[].class);    // 不为Object数组的话就进行复制
    }

ArrayList 核心方法分析

 

添加元素  add(E e)

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

从源码中看到在添加元素时调用ensureCapacityInternal方法,这个方法呢就是为了保证存储数组的大小够用,不会导致数组越界,下面具体看下ensureCapacityInternal实现

    private void ensureCapacityInternal(int minCapacity) {
        modCount++;    // 结构性修改加1
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)    //添加元素时长度 - 存储数组长度是否大于0,如果大于0说明数组大小够用,小于0则需要对存储数组扩容
            grow(minCapacity);
    }

如果存储数组需要进扩容操作,那么就会调用grow方法,这个方法会产生一个新的数组重新赋值给存储数组,新的的数组长度是原数组的1.5倍。下面具体看下grow实现

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;    // 原来的容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);    // 新的容量扩展为原来容量的1.5倍
        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);  // 创建一个新的容量数组赋值给存储数组
    }

 

访问元素  get(index)

    public E get(int index) {
        rangeCheck(index);    // 检验索引是否合法

        return elementData(index);
    }

    在根据索引获取元素时,首先进行索引值的判断,索引值大于实际size的大小则抛出异常,否则继续根据索引值获取存储数组中的对象,rangeCheck实现

    private void rangeCheck(int index) {  
        if (index >= size)    // 索引值大于实际size的大小则抛出异常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

   根据索引值获取存储数组中的对象,elementData实现

    E elementData(int index) {
        return (E) elementData[index];    // 返回存储数组对应索引的值,向下转型(Object -> E)
    }

 

修改元素  set(int index, E element)

    public E set(int index, E element) {
        rangeCheck(index);  // 检验索引是否合法

        E oldValue = elementData(index);   // 原始值
        elementData[index] = element;     // 把存储数组索引为index的值改为新值
        return oldValue;     // 返回原始值
    }

 

插入元素  add(int index, E element)

    public void add(int index, E element) {
        rangeCheckForAdd(index);  // 检验索引是否合法

        ensureCapacityInternal(size + 1);  // 需要扩容扩容操作
      // 拷贝到从下标为index+1位置开始的新的elementData数组中。  
      // 即将当前位于该位置的元素以及所有后续元素右移一个位置。  
        System.arraycopy(elementData, index, elementData, index + 1,    
                         size - index);
        elementData[index] = element;
        size++;    // 实际元素长度+1
    }

     由于ArrayList 是个动态数组,插入元素时需要移动指针操作,所以插入元素效率低。

 

删除指定位置元素   remove(int index)

    public E remove(int index) {
        rangeCheck(index);    // 检验索引是否合法

        modCount++;    // 结构性修改加1
        E oldValue = elementData(index);  // 获取原来的值

        int numMoved = size - index - 1;  // 需要移动的元素的个数
        if (numMoved > 0)
System.arraycopy(elementData, index
+1, elementData, index, numMoved); elementData[--size] = null; // 最后一个元素设置为空 return oldValue;    // 返回原始值 }

删除元素时同样需要移动指针操作,删除元素效率低。

删除元素时首先判断是否为最后一个元素,如果不是最后一个,就按指定的索引从存储数组删除,索引后面所有元素左移一个位置,并把最后一个元素设置为null以便GC进行回收

 

 总结


很多技术,不只是只会用就行,研究它底层实现其实是很有趣的一件事,同时技术能够得到一定的提升。下篇继续学习LinkedList

如果有不足之处请指出,谢谢!

版权声明:本文为Dylanl原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/Dylanl/p/8722729.html