Java容器框架(二)--ArrayList实现原理

1. 简介

Java容器框架(一)--概述篇 中,对ArrayList做了一些简单的介绍,它在List家族中具有很重要的角色,它的类继承关系如下:

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

本篇文章中将会详细讲解ArrayList的实现原理,分析其原理当然是从其方法入手,本文涉及到的方法主要如下图所示:


2. ArrayList() & ArrayList(int initialCapacity)

这两个方法都属于ArrayList的构造函数,一个是默认无参构造,一个是指定一个初始大小的有参构造,下面我们先来分析无参构造函数。

  • ArrayList()
// 默认时候 容器的大小
private static final int DEFAULT_CAPACITY = 10;
// 默认情况  空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 保存元素的数组
transient Object[] elementData; // non-private to simplify nested class access
// 容器中元素的大小
private int size;


public ArrayList() {
    super();     // 调用的是父类(AbstractList)的构造函数
    this.elementData = EMPTY_ELEMENTDATA;
}

protected AbstractList() {
}

上面代码中已经给予了充分的注释,在对象初始化的时候,定义一个初始容器常量大小DEFAULT_CAPACITY为10,初始化一个为空的数组常量(这两个参数主要用于默认情况下使用),对于无参构造函数,仅仅只是初始化了当前容器中的元素集合为一个空数组

那么有参构造是怎样的呢?

public ArrayList(int initialCapacity) {
     super();
     if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
     this.elementData = new Object[initialCapacity];
}

同样,代码也非常简单,初始化容器中存放元素的数组大小为传入的initialCapacity,注意此处并没有设置size的值等于initialCapacity,因为size是表示容器中元素的个数。


3. boolean add(E e) & add(int index, E element)

通过方法名就可以知道,这两个方法都是向容器中添加元素,它俩有啥区别呢?下面通过源码来详细讲解,先来看看add(E e) 的实现。

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

private void ensureCapacityInternal(int minCapacity) {
    // 判断当前容器是否为空,为空则初始化一个传入值和默认值中大的那个数作为初始化数组的大小
    if (elementData == EMPTY_ELEMENTDATA) {
       minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

// 该变量定义在AbstractList类中,主要用于记录列表记录的次数
protected transient int modCount = 0;

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

    // 如果所需容量大于当前数组的容量,则需要对数组进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// 容器中最大容量,这里减8的意义在于部分虚拟机内存中存放数组需要存放一个头部信息,
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 扩容策略,扩大到之前容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 扩容1.5倍后容量还是不够,则选择传递过来的数据作为容量大小
    if (newCapacity - minCapacity < 0)
         newCapacity = minCapacity;
    // 新的容量如果大于最大容量,则需要进行再次检查
    if (newCapacity - MAX_ARRAY_SIZE > 0)
         newCapacity = hugeCapacity(minCapacity); // 再一次扩充容量到最大
     // 进行扩容,将elementData容量扩大至newCapacity大小,并复制原有的数据到新的elementData 
     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;
}

代码注释非常清楚,这里做一个总结:向ArrayList添加一个元素,首先会判断当前容器是否能够存放该元素(也就是说容器是否已满),如果存放不下,则会扩容,扩容的策略就是在原来大小的基础上扩大到1.5倍,当然这个扩容并不是无限制的,由于考虑虚拟机的特性,数组的最大值为Integer.MAX_VALUE - 8(最最大值为Integer.MAX_VALUE),正常扩容成功后,则调用Arrays.copyOf函数,产生一个新的数组,数组大小为扩容后的大小,同时将原有的元素拷贝到新的数组里面。

这里有一个需要注意的是变量size和数组容量大小的区别:size是表示数组中存放了多少元素,数组容量是指数组可以存放多少元素,两者是不一样的概念。

  • add(int index, E element)

add(int index, E element)作用就是向指定位置添加一个元素,下面再来看看的源码实现:

public void add(int index, E element) {
    // 对插入的位置进行检查,插入的位置小于0以及位置大于当前元素的个数,都会抛出异常
    rangeCheckForAdd(index);

    // 该函数已经分析过,主要作用就是检查是否需要扩容及进行扩容等操作
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 该函数是一个native方法,作用是elementData数组中位置为index(包含)之后的元素后移
    System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
    elementData[index] = element;
    size++;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/*
 * @param      src      the source array.
 * @param      srcPos   starting position in the source array.
 * @param      dest     the destination array.
 * @param      destPos  starting position in the destination data.
 * @param      length   the number of array elements to be copied.
 *  将源数组中srcPos位置开始之后的length长度数据拷贝到dest数组的destPos开始之后的位置
*/
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

注释依然非常清晰,和add(E e)原理差不多,唯一区别就是在某个位置插入添加元素,对于数组当然是需要把该位置及之后的元素后移,因此相对而言,这种指定某个位置添加元素效率是非常低的。


4. contains(Object o) & remove(int index)

有了前面的基础,这两个函数的实现就非常简单了,下面仍然通过源码来分析分析。

  • contains(Object o): 是否包含该元素,包含则返回具体的位置,否则返回-1
public boolean contains(Object o) {
     return indexOf(o) >= 0;
}

public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                    return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                  return i;
    }
    return -1;
}

代码很简单,遍历数组,查找数组中存放的元素与传入的元素是否相等(通过equals比较),没有找到这返回-1,时间复杂度为n。

  • remove(int index) : 删除某个位置的元素,返回删除的元素
public E remove(int index) {
    // 检查index不能大于size,否则抛出异常
   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;
}

删除元素,也是需要将删除之后的位置元素前移,时间复杂度为n。


5. iterator() & listIterator()

在ArrayList中我们可以看到有这两个方法,iterator()返回的是Itr对象,listIterator()返回的是ListItr对象,这两种都属于迭代器范畴,我们可以看看两者的类结构:

private class Itr implements Iterator<E>
private class ListItr extends Itr implements ListIterator<E>
public interface ListIterator<E> extends Iterator<E>

可见ListItr在Itr基础再次进行扩展,ListItr迭代器只有List容器才有,迭代器也是用于遍历容器中的元素,但是对于ArrayList这种采用数组来实现的容器,并不推荐使用迭代器来遍历数组。

至此,对ArrayList的分析就结束了。

 

参考文献

http://www.trinea.cn/android/arraylist-linkedlist-loop-performance/

https://www.cnblogs.com/xiangkejin/p/6752298.html

https://blog.csdn.net/u013309870/article/details/72519272

猜你喜欢

转载自blog.csdn.net/u010349644/article/details/82789545